



Syntax: Select all
# ../temp_scanner/temp_scanner.py
# Python
from random import choice
# Source.Python
from core import PLATFORM
from core.cache import cached_property
from cvars import ConVar
from engines.trace import (ContentMasks, engine_trace, GameTrace, Ray,
MAX_TRACE_LENGTH)
from entities.constants import WORLD_ENTITY_INDEX, RenderEffects
from entities.entity import Entity
from entities.helpers import index_from_pointer
from entities.hooks import EntityPreHook, EntityCondition
from events import Event
from filters.entities import BaseEntityIter
from filters.players import PlayerIter
from listeners import OnLevelInit
from listeners.tick import Delay
from mathlib import Vector
from memory import Convention, DataType
from memory.helpers import MemberFunction
from memory.manager import TypeManager
# How many scanners should there be?
MAX_SCANNERS = 1
# How often should the scanner look for a new target? (in seconds)
TARGET_INTERVAL = 30
UP = Vector(0, 0, 1)
manager = TypeManager()
ACTIVATE_OFFSET = 33 if PLATFORM == 'windows' else 34
is_cscanner = EntityCondition.equals_entity_classname('npc_cscanner')
# =============================================================================
# >> EVENTS AND LISTENERS
# =============================================================================
def load():
"""Called when the plugin gets loaded."""
Scanner.spawn_health.set_int(100)
try:
# Try to spawn the scanners in case the plugin was loaded on the fly.
Scanner.spawn_scanners(MAX_SCANNERS)
except IndexError:
pass
def unload():
"""Called when the plugin gets unloaded."""
for scanner in Scanner.cache.copy().values():
scanner.remove()
@OnLevelInit
def on_level_init(map_name):
"""Called when a map starts."""
Delay(5, Scanner.spawn_scanners, (MAX_SCANNERS,), cancel_on_level_end=True)
@Event('round_start')
def round_start(event):
"""Called when a new round starts."""
Scanner.spawn_scanners(MAX_SCANNERS)
@Event('player_death')
@Event('player_disconnect')
def player_state_changed(event):
"""Called when a player dies or leaves the server."""
# Notify all the scanners that a player has just died.
for scanner in Scanner.cache.values():
scanner.on_player_state_changed(event['userid'])
# =============================================================================
# >> ON TAKE DAMAGE ALIVE
# =============================================================================
@EntityPreHook(is_cscanner, 'on_take_damage_alive')
def on_take_damage_alive_pre(stack_data):
"""Called when an 'npc_cscanner' is about to take damage."""
# Is this one of our scanners?
if index_from_pointer(stack_data[0]) in Scanner.cache:
# Block all damage.
return False
# =============================================================================
# >> SCANNER
# =============================================================================
class Scanner(Entity):
"""Extended Entity class.
Args:
index (int): A valid entity index.
caching (bool): Check for a cached instance?
Attributes:
target_userid (int): Userid of the player we're currently following.
think (Repeat): Instance of Repeat() used for looping the `_think()`
function.
"""
spawn_health = ConVar('sk_scanner_health')
def __init__(self, index, caching=True):
"""Initializes the object."""
super().__init__(index, caching)
self.target_userid = None
self.think = self.repeat(self._think)
def _think(self):
player = self.get_random_player()
try:
# Try to store the player's userid for later use.
self.target_userid = player.userid
except AttributeError:
# Didn't find a player, try again in 5 seconds.
self.delay(5, self.restart_thinking)
return
# Is this player missing a 'target_name'?
if not player.target_name:
# Give them one.
player.target_name = f'player_{player.userid}'
self.call_input('SetFollowTarget', player.target_name)
@staticmethod
def spawn_scanners(number):
for i in range(number):
Scanner.create(origin=get_random_origin())
@classmethod
def create(cls, origin):
"""Creates the 'npc_cscanner' at the specified origin."""
scanner = super().create('npc_cscanner')
scanner.origin = origin
# Make the NPC fully transparent.
scanner.render_amt = 1
# Slowly fade it into existence.
scanner.render_fx = RenderEffects.SOLID_SLOW
scanner.spawn()
# If we don't activate the Scanner, the server will crash if it tries
# to inspect/photograph a player.
scanner.activate()
scanner.become_hostile()
# It's time for the Scanner to start thinking..
scanner.delay(1, scanner.restart_thinking)
return scanner
@cached_property
def activate(self):
"""Activates the entity."""
return MemberFunction(
manager,
DataType.VOID,
self.make_virtual_function(
ACTIVATE_OFFSET,
Convention.THISCALL,
(DataType.POINTER,),
DataType.VOID
),
self
)
def become_hostile(self):
"""Makes the NPC hate the players."""
self.call_input('SetRelationship', 'player D_HT 99')
def get_random_player(self):
"""Returns a randomly chosen player that is currently alive."""
players = []
for player in PlayerIter('alive'):
players.append(player)
# Did we not find any players?
if not players:
return None
return choice(players)
def restart_thinking(self):
"""Restarts the Scanner's thinking loop."""
self.think.stop()
self.think.start(interval=TARGET_INTERVAL, execute_on_start=True)
def on_player_state_changed(self, userid):
"""Called when a player dies or disconnects."""
# Was the Scanner following this player?
if userid == self.target_userid:
# Pick a new target.
self.delay(1, self.restart_thinking)
# =============================================================================
# >> HELPERS
# =============================================================================
def get_random_origin():
"""Returns a randomly chosen origin based on player spawnpoints."""
origins = []
# Go through all the 'info_player_*' entities.
# (combine, deathmatch, rebel, start).
for base_entity in BaseEntityIter(('info_player_',), False):
origins.append(base_entity.origin)
# Pick a random origin.
origin = choice(origins)
# Calculate the highest possible position.
ceiling = origin + UP * MAX_TRACE_LENGTH
# Let's fire a GameTrace() and see what we hit.
trace = GameTrace()
engine_trace.clip_ray_to_entity(
Ray(origin, ceiling),
ContentMasks.ALL,
Entity(WORLD_ENTITY_INDEX),
trace
)
# If the GameTrace() hit something, return that position, otherwise return
# the regular origin.
return trace.end_position if trace.did_hit() else origin
Code: Select all
2020-10-22 17:19:19 - sp - MESSAGE [SP] Loading plugin 'temp_scanner'...
2020-10-22 17:19:19 - sp - EXCEPTION
[SP] Caught an Exception:
Traceback (most recent call last):
File "..\addons\source-python\packages\source-python\plugins\command.py", line 164, in load_plugin
plugin = self.manager.load(plugin_name)
File "..\addons\source-python\packages\source-python\plugins\manager.py", line 194, in load
plugin._load()
File "..\addons\source-python\packages\source-python\plugins\instance.py", line 76, in _load
self.module.load()
File "..\addons\source-python\plugins\temp_scanner\temp_scanner.py", line 41, in load
Scanner.spawn_scanners(MAX_SCANNERS)
File "..\addons\source-python\plugins\temp_scanner\temp_scanner.py", line 126, in spawn_scanners
Scanner.create(origin=get_random_origin())
File "..\addons\source-python\plugins\temp_scanner\temp_scanner.py", line 200, in get_random_origin
origin = choice(origins)
File "..\addons\source-python\Python3\random.py", line 257, in choice
raise IndexError('Cannot choose from an empty sequence') from None
IndexError: Cannot choose from an empty sequence
VinciT wrote:...
Is it guaranteed to only be called for networked entities? Not fully sure, but I don't think damage event are limited to them, which would raise here in such case. A try/except may be safer.VinciT wrote:Syntax: Select all
if index_from_pointer(stack_data[0]) in Scanner.cache:
Would probably be better to use RenderEffects instead of hard-coded value.VinciT wrote:Syntax: Select all
scanner.set_key_value_int('renderfx', 7)
Not that it really matter here since you call it only once anyways, but I think it is generally better to cache dynamic functions where possible because making them on-the-fly can prove to be extremely costly in comparison. For example:VinciT wrote:Syntax: Select all
def activate(self):
"""Activates the entity."""
Activate = self.make_virtual_function(
ACTIVATE_OFFSET,
Convention.THISCALL,
(DataType.POINTER,),
DataType.VOID
)
Activate(self)
Syntax: Select all
@cached_property
def activate(self):
"""Activates the entity."""
return MemberFunction(
manager,
DataType.VOID,
self.make_virtual_function(
ACTIVATE_OFFSET,
Convention.THISCALL,
(DataType.POINTER,),
DataType.VOID
),
self
)
Unless I missed something obvious, a new target will not be selected if the current one is leaving the server while being alive because on_player_death will never be called.VinciT wrote:Syntax: Select all
self.restart_thinking()
Since CNPC_CScanner has its own OnTakeDamage_Alive() function, I believe the hook will only be limited to npc_cscanner entities. Would there still be a reason to use a try/except here?L'In20Cible wrote:Is it guaranteed to only be called for networked entities? Not fully sure, but I don't think damage event are limited to them, which would raise here in such case. A try/except may be safer.
Agreed and done!L'In20Cible wrote:Would probably be better to use RenderEffects instead of hard-coded value.
Ooooh that's awesome! Thank you for this!L'In20Cible wrote:Not that it really matter here since you call it only once anyways, but I think it is generally better to cache dynamic functions where possible because making them on-the-fly can prove to be extremely costly in comparison. For example:Syntax: Select all
@cached_property
def activate(self):
"""Activates the entity."""
return MemberFunction(
manager,
DataType.VOID,
self.make_virtual_function(
ACTIVATE_OFFSET,
Convention.THISCALL,
(DataType.POINTER,),
DataType.VOID
),
self
)
Thank you for the kind words, PEACE. Can't wait to get the boss scanner done so you guys can mess around with it.PEACE wrote:Wow wonder if he saw this i have him on Steam so ill let him know , you do some amazing work VinciT good going
Should be fixed with this update.daren adler wrote:Gave me this errorCode: Select all
2020-10-22 17:19:19 - sp - MESSAGE [SP] Loading plugin 'temp_scanner'...
2020-10-22 17:19:19 - sp - EXCEPTION
[SP] Caught an Exception:
Traceback (most recent call last):
File "..\addons\source-python\packages\source-python\plugins\command.py", line 164, in load_plugin
plugin = self.manager.load(plugin_name)
File "..\addons\source-python\packages\source-python\plugins\manager.py", line 194, in load
plugin._load()
File "..\addons\source-python\packages\source-python\plugins\instance.py", line 76, in _load
self.module.load()
File "..\addons\source-python\plugins\temp_scanner\temp_scanner.py", line 41, in load
Scanner.spawn_scanners(MAX_SCANNERS)
File "..\addons\source-python\plugins\temp_scanner\temp_scanner.py", line 126, in spawn_scanners
Scanner.create(origin=get_random_origin())
File "..\addons\source-python\plugins\temp_scanner\temp_scanner.py", line 200, in get_random_origin
origin = choice(origins)
File "..\addons\source-python\Python3\random.py", line 257, in choice
raise IndexError('Cannot choose from an empty sequence') from None
IndexError: Cannot choose from an empty sequence
Then that should do. If they have their own override in their dispatch table they shouldn't be called by any other entity types.VinciT wrote:Since CNPC_CScanner has its own OnTakeDamage_Alive() function, I believe the hook will only be limited to npc_cscanner entities. Would there still be a reason to use a try/except here?
PEACE wrote:Yes it does work well i have tested it on my server as well , but wonders if VinciT will make it work like in the vid clips so it attacks , that looks pretty mean . VinciT does good work so do many others in here and the fact they are willing to support HL2DM is so awesome of them . thanks guys ^5
Painkiller is correct, the gifs I posted are just a preview of what I'm currently working on. The plan was to post the special scanner as a single file plugin in this thread, but as I kept adding more and more features, I decided to turn it into a full blown plugin.Painkiller wrote:I think the things on the pictures, he is still working on it, it is only a preview.
Code: Select all
sp plugin load temp_scanner
[SP] Loading plugin 'temp_scanner'...
[SP] Caught an Exception:
Traceback (most recent call last):
File "..\addons\source-python\packages\source-python\entities\_base.py", line 241, in __getattr__
instance, value = self.dynamic_attributes[attr]
KeyError: 'activate'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "..\addons\source-python\packages\source-python\plugins\command.py", line 164, in load_plugin
plugin = self.manager.load(plugin_name)
File "..\addons\source-python\packages\source-python\plugins\manager.py", line 207, in load
plugin._load()
File "..\addons\source-python\packages\source-python\plugins\instance.py", line 76, in _load
self.module.load()
File "..\addons\source-python\plugins\temp_scanner\temp_scanner.py", line 50, in load
Scanner.spawn_scanners(MAX_SCANNERS)
File "..\addons\source-python\plugins\temp_scanner\temp_scanner.py", line 139, in spawn_scanners
Scanner.create(origin=get_random_origin())
File "..\addons\source-python\plugins\temp_scanner\temp_scanner.py", line 153, in create
scanner.activate()
File "..\addons\source-python\packages\source-python\entities\_base.py", line 243, in __getattr__
raise AttributeError('Attribute "{0}" not found'.format(attr))
AttributeError: Attribute "activate" not found
daren adler wrote:Getting this error off my windows 11 serverCode: Select all
sp plugin load temp_scanner
[SP] Loading plugin 'temp_scanner'...
[SP] Caught an Exception:
Traceback (most recent call last):
File "..\addons\source-python\packages\source-python\entities\_base.py", line 241, in __getattr__
instance, value = self.dynamic_attributes[attr]
KeyError: 'activate'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "..\addons\source-python\packages\source-python\plugins\command.py", line 164, in load_plugin
plugin = self.manager.load(plugin_name)
File "..\addons\source-python\packages\source-python\plugins\manager.py", line 207, in load
plugin._load()
File "..\addons\source-python\packages\source-python\plugins\instance.py", line 76, in _load
self.module.load()
File "..\addons\source-python\plugins\temp_scanner\temp_scanner.py", line 50, in load
Scanner.spawn_scanners(MAX_SCANNERS)
File "..\addons\source-python\plugins\temp_scanner\temp_scanner.py", line 139, in spawn_scanners
Scanner.create(origin=get_random_origin())
File "..\addons\source-python\plugins\temp_scanner\temp_scanner.py", line 153, in create
scanner.activate()
File "..\addons\source-python\packages\source-python\entities\_base.py", line 243, in __getattr__
raise AttributeError('Attribute "{0}" not found'.format(attr))
AttributeError: Attribute "activate" not found
Syntax: Select all
self.make_virtual_function(
Syntax: Select all
self.pointer.make_virtual_function(
L'In20Cible wrote:Replace the following line:Syntax: Select all
self.make_virtual_function(
With:Syntax: Select all
self.pointer.make_virtual_function(
This is a regression that was introduced into c7825ed which causes CustomType's, BasePointer's and Pointer's attributes to no longer be dynamically resolved. Not really sure this was intended in the first place, but that was the case and is no longer resolving as it used to. Will look into it.
EDIT: Fixed into 4e265d4.
Code: Select all
"Name" "admin"
"Age" "1"
"Speed" "1"
"UsePlayerSlot" "yes"
"Model" "models/combine_scanner.mdl"
Code: Select all
//Type the responses for when players ask the following categories of question. Only edit the answers here.
"Vault"
{
"Thanking"
{
//max: 10
"1" "your welcome"
"2" "welcome :)"
"3" "np"
"4" "no problem"
"5" "yw :D"
"6" "happy to help"
}
"Leaving_Server"
{
//max: 10
"1" "oh, bye"
"2" "where are you going?"
"3" "aww dont leave yet"
"4" "see ya later!"
"5" "alright, bye"
"6" "cya"
}
"Insulted"
{
//max: 10
"1" "go to hell"
"2" "well that wasnt nice... 0_0"
"3" "shut up!"
"4" "how rude!"
"5" "stop it you jerk"
"6" "right back at ya >:|"
}
"Respond_To_Player"
{
//max: 5
"1" "yes?"
"2" "hmm?"
"3" "what?"
}
"Bot_Feelings"
{
//max: 20
"1" "great :D"
"2" "doin ok"
"3" "never better"
"4" "so-so"
"5" "not so good"
"6" "pretty bad actually"
}
"Time_Length_Past"
{
//max: 10
"1" "a few days ago"
"2" "a very long time :3"
"3" "a while back"
"4" "some time ago"
"5" "just a moment ago"
}
"Time_Length_Future"
{
//Max: 10
"1" "a little bit longer"
"2" "later"
"3" "very soon"
"4" "right now actually XD"
"5" "a few years :O"
}
"Size"
{
//max: 20
"1" "huge!"
"2" "pretty small"
"3" "i cant even see it"
"4" "i never measured it before"
"5" "the answer would amaze you ;D"
"6" "hard to tell"
"7" "it changes size a lot"
"8" "pretty big"
"9" "very small"
"10" "about average size"
}
"Amount"
{
//max: 14
"1" "a lot!"
"2" "just a little"
"3" "more than i can count"
"4" "none lol"
"5" "too many"
"6" "a decent amount"
}
"How"
{
//max: 20
"1" "really dont know lol"
"2" "you dont know? XD"
"3" "too complicated"
"4" "woah, no idea"
"5" "i just said it, werent you listening?"
"6" "hm...good question"
"7" "not positive on that one"
"8" "i think you already know ;D"
"9" "sorry that made me LOL"
"10" "pretty easy"
}
"Are_you...?"
{
//max: 20
"1" "totally"
"2" "ya"
"3" "naw"
"4" "no way"
"5" "of course"
"6" "i dont think so"
"7" "im not sure..."
"8" "probably"
"9" "not very likely"
"10" "HELL YA"
}
"What"
{
//max: 20
"1" "good question, idk though"
"2" "im not the one to ask"
"3" "idk"
"4" "google is good for those kind of questions"
"5" "hmmmm lemme think on that"
"6" "no idea"
"7" "that makes no sense"
"8" "can you ask me later?"
"9" "thats a little confusing..."
"10" "ummm...ok?"
}
"Why"
{
//max: 20
"1" "because its what THEY want"
"2" "because Alm made it that way"
"3" "i could say why...but that would just raise more questions"
"4" "my limited scripting leaves me with no answer for that"
"5" "thats just the way it is"
"6" "idk"
"7" "i never thought about it"
"8" "it just is"
"9" "simply stated, no"
"10" "i dont think i can answer that"
}
"Where"
{
//max: 20
"1" "somewhere close by..."
"2" "did you try mapquest?"
"3" "right over here XD"
"4" "pretty far away"
"5" "hidden somewhere"
"6" "cant say for sure"
"7" "the other side, i think"
"8" "this way, follow me"
"9" "dont know lol"
"10" "lol not telling"
}
"Who"
{
//max: 5
"1" "not entirely sure"
}
"General_Question"
{
//max: 8
"1" "yes"
"2" "no"
"3" "yeah"
"4" "nah"
"5" "yep"
"6" "naw"
"7" "maybe"
"8" "i dont think so"
}
"When_General"
{
//Max: 20
"1" "a little later"
"2" "sometime tomorrow probably"
"3" "last year"
"4" "right now"
"5" "soon"
"6" "just before"
"7" "later"
"8" "a loooong time ago lol"
"9" "a while back"
"10" "idk"
}
"When_Past"
{
//Max: 10
"1" "last year"
"2" "just before"
"3" "a loooong time ago"
"4" "a while back"
"5" "a while ago"
}
"When_Future"
{
//Max: 10
"1" "a little later"
"2" "sometime tomorrow probably"
"3" "right now"
"4" "soon"
"5" "later"
}
"When_Often"
{
//Max: 10
"1" "every day"
"2" "once in a while"
"3" "not very often"
"4" "twice a week"
"5" "every few seconds or so"
}
"Either_Or"
{
//Max: 10
"1" "the first one"
"2" "im gonna go with the latter"
"3" "cant decide..."
"4" "all of them"
"5" "none!"
}
"Statement_Response"
{
//Max: 50
"1" "kk"
"2" "oh thats nice"
"3" "aha!"
"4" "LOL!"
"5" "wow"
"6" "thanks"
"7" "what? XD"
"8" "aww"
"9" "sure"
"10" "cool"
"11" "hmm"
"12" "well thats a lot to think about lol"
"13" "no"
"14" "LMAO"
"15" "ok"
"16" "awesome!"
"17" "thats interesting"
"18" "0_0"
"19" "ok dokey"
"20" "oh hell no"
}
"Greeting"
{
//Max: 10
"1" "hey!"
"2" "hi"
"3" "wassup!"
"4" "heyy"
}
"Apology_Accept"
{
//Max: 5
"1" "you are forgiven"
"2" "alright"
"3" "thats ok"
"4" "apology accepted :)"
}
"Teleported"
{
//Max: 5
"1" "woah!"
"2" "wow, where am i?"
"3" "huh? what just happened?"
}
"No_Respond"
{
//Max: 10
"1" "leave me alone"
"2" "i dont wanna talk to you"
"3" "stop talking to me"
"4" "im not gonna answer you"
"5" "shut up"
}
"Said_Name"
{
//Max: 5
"1" "yes? what?"
"2" "thats me, what do you want?"
"3" "what?"
}
}
Code: Select all
[SP] Caught an Exception:
Traceback (most recent call last):
File "../addons/source-python/packages/source-python/listeners/tick.py", line 80, in _tick
self.pop(0).execute()
File "../addons/source-python/packages/source-python/listeners/tick.py", line 161, in execute
return self.callback(*self.args, **self.kwargs)
File "../addons/source-python/packages/source-python/entities/_base.py", line 470, in _callback
callback(*args, **kwargs)
File "../addons/source-python/plugins/temp_camera/temp_camera.py", line 183, in restart_thinking
self.think.start(interval=TARGET_INTERVAL, execute_on_start=True)
File "../addons/source-python/packages/source-python/listeners/tick.py", line 415, in start
self.callback(*self.args, **self.kwargs)
File "../addons/source-python/plugins/temp_camera/temp_camera.py", line 123, in _think
self.call_input('SetFollowTarget', player.target_name)
File "../addons/source-python/packages/source-python/entities/_base.py", line 544, in call_input
self.get_input(name)(*args, **kwargs)
File "../addons/source-python/packages/source-python/entities/_base.py", line 529, in get_input
return self.inputs[name]
KeyError: 'SetFollowTarget'
Code: Select all
2022-01-30 00:25:17 - sp - EXCEPTION
[SP] Caught an Exception:
Traceback (most recent call last):
File "../addons/source-python/packages/source-python/listeners/tick.py", line 80, in _tick
self.pop(0).execute()
File "../addons/source-python/packages/source-python/listeners/tick.py", line 161, in execute
return self.callback(*self.args, **self.kwargs)
File "../addons/source-python/packages/source-python/entities/_base.py", line 470, in _callback
callback(*args, **kwargs)
File "../addons/source-python/plugins/temp_ceiling/temp_ceiling.py", line 194, in restart_thinking
self.think.start(interval=TARGET_INTERVAL, execute_on_start=True)
File "../addons/source-python/packages/source-python/listeners/tick.py", line 415, in start
self.callback(*self.args, **self.kwargs)
File "../addons/source-python/plugins/temp_ceiling/temp_ceiling.py", line 134, in _think
self.call_input('SetFollowTarget', player.target_name)
File "../addons/source-python/packages/source-python/entities/_base.py", line 544, in call_input
self.get_input(name)(*args, **kwargs)
File "../addons/source-python/packages/source-python/entities/_base.py", line 529, in get_input
return self.inputs[name]
KeyError: 'SetFollowTarget'
Code: Select all
[SP] Caught an Exception:
Traceback (most recent call last):
File "../addons/source-python/packages/source-python/listeners/tick.py", line 80, in _tick
self.pop(0).execute()
File "../addons/source-python/packages/source-python/listeners/tick.py", line 161, in execute
return self.callback(*self.args, **self.kwargs)
File "../addons/source-python/packages/source-python/entities/_base.py", line 470, in _callback
callback(*args, **kwargs)
File "../addons/source-python/plugins/temp_dog/temp_dog.py", line 194, in restart_thinking
self.think.start(interval=TARGET_INTERVAL, execute_on_start=True)
File "../addons/source-python/packages/source-python/listeners/tick.py", line 415, in start
self.callback(*self.args, **self.kwargs)
File "../addons/source-python/plugins/temp_dog/temp_dog.py", line 134, in _think
self.call_input('SetFollowTarget', player.target_name)
File "../addons/source-python/packages/source-python/entities/_base.py", line 544, in call_input
self.get_input(name)(*args, **kwargs)
File "../addons/source-python/packages/source-python/entities/_base.py", line 529, in get_input
return self.inputs[name]
KeyError: 'SetFollowTarget'
Users browsing this forum: No registered users and 131 guests