[HL2:DM] Little Silent Hill

A place for requesting new Source.Python plugins to be made for your server.

Please request only one plugin per thread.
User avatar
VinciT
Senior Member
Posts: 268
Joined: Thu Dec 18, 2014 2:41 am

Re: [HL2:DM] Little Silent Hill

Postby VinciT » Thu Oct 01, 2020 11:16 pm

If the models are T posing that means they are missing animations required by the NPC. In other words, they were made either for the player or some other NPC. Animations can be played through code, but it's much better to have them properly setup within the model.
ImageImageImageImageImage
User avatar
daren adler
Senior Member
Posts: 124
Joined: Sat May 18, 2019 7:42 pm
Contact:

Re: [HL2:DM] Little Silent Hill

Postby daren adler » Fri Oct 02, 2020 2:08 am

VinciT wrote:If the models are T posing that means they are missing animations required by the NPC. In other words, they were made either for the player or some other NPC. Animations can be played through code, but it's much better to have them properly setup within the model.


I can answer that, they were made for player and can be used for bots. i got them from gmod and used gmad to make the folders . They are the same ones i sent to Painkiller. Here is where i got the nurse https://steamcommunity.com/sharedfiles/ ... ilent+hill and here is where i got the Pyramid Head https://steamcommunity.com/sharedfiles/ ... ilent+hill .
User avatar
VinciT
Senior Member
Posts: 268
Joined: Thu Dec 18, 2014 2:41 am

Re: [HL2:DM] Little Silent Hill

Postby VinciT » Fri Oct 02, 2020 3:46 am

I'd love to help, but I have no experience with animations. Your best bet is to either find NPC specific models on the workshop (maybe even gamebanana/moddb) or find someone who can properly rig and animate those models.
ImageImageImageImageImage
User avatar
Painkiller
Senior Member
Posts: 630
Joined: Sun Mar 01, 2015 8:09 am
Location: Germany
Contact:

Re: [HL2:DM] Little Silent Hill

Postby Painkiller » Fri Oct 02, 2020 7:47 am

VinciT wrote:Okay, the way you have it setup right now, the pyramid head model is being set for the npc_zombie, and the bubble nurse model for the npc_headcrab - which is why the nurse is moving like a headcrab. If your intention was to set both models for the npc_zombie, replace the dictionary with this:

Syntax: Select all

NPC_MODELS = {
'npc_zombie': (
Model('models/pyramid_head.mdl'),
Model('models/psychedelicum/silent_hill/bubble_nurse/npc/bubble_nurse_combine.mdl')
)
}
If that's not the case, replace npc_headcrab in your dictionary with the name of the NPC that's supposed to have the nurse model.

Ok I thought the models have their own animations.
I didn't know that they were player models and not npc models
User avatar
PEACE
Junior Member
Posts: 23
Joined: Mon Oct 12, 2020 1:13 pm

Re: [HL2:DM] Little Silent Hill

Postby PEACE » Sat Nov 21, 2020 11:56 am

I really like this mod and use it , I have a question ? Is it possible to have the bots kill the npc's they just get killed by them and dont react to them at all and this is fine if its too much to ask for .

thanks

It just came to mind that maybe it easyer to make the NPC's ignore bots and just attack human players ...

\PEACE
User avatar
VinciT
Senior Member
Posts: 268
Joined: Thu Dec 18, 2014 2:41 am

Re: [HL2:DM] Little Silent Hill

Postby VinciT » Mon Nov 23, 2020 3:46 am

PEACE wrote:It just came to mind that maybe it easyer to make the NPC's ignore bots and just attack human players ...
Yep, you're spot on. The bots were probably made to only look for players when selecting their target. You could try asking the creator of the bot plugin to include additional targeting for NPCs. Now let's try to make NPCs ignore bots.

Find the seek_random_player() function and change this line:

Syntax: Select all

if player.dead:
To this:

Syntax: Select all

if player.dead or player.is_bot():

This probably won't work completely, but it will stop the NPCs from selecting bots as their first target. I'll find a better way for excluding bots when I get up tomorrow. Also, could you send me a link to the bot plugin you're using? I want to poke around a bit and see if I can make the bots attack NPCs with SP.
ImageImageImageImageImage
User avatar
PEACE
Junior Member
Posts: 23
Joined: Mon Oct 12, 2020 1:13 pm

Re: [HL2:DM] Little Silent Hill

Postby PEACE » Mon Nov 23, 2020 4:46 am

Well here is the issue the bots were made by Hurrican and he left years ago i have tried to reach him and he is the only one with the source code for them sadly if Valve does a major SDK update it could kill them again and we rely on them ... I wish someone could duplicate them since they are the only bots who auto spawn point and way point the maps and make a file for them much like the bots in CSS. im not sure if anyone here could un assemble the C++ they were made in and many servers still use them .
I will try what you posted even if it reduces the kill rate that is a huge plus for me. his download site is down but if you ever want me to send you a copy of them let me know .

Thanks VinciT
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
update , not much of a change maybe there is also a way if im in spawn protection the NPC's wont keep attacking also , how the bots work is they wont attack human players with 500 health so when in spawn prot. we have 500 until we shoot or move then its 100 and i think its also some form of god mode so human players cant kill you . im not using the one from here its a modified ver of a source mod plugin . we like it because it displays in hud text .

//PEACE
User avatar
VinciT
Senior Member
Posts: 268
Joined: Thu Dec 18, 2014 2:41 am

Re: [HL2:DM] Little Silent Hill

Postby VinciT » Mon Nov 23, 2020 11:22 pm

Found the bot plugin online, but sadly I wasn't able to get the bots to attack the NPCs. However, this should prevent the NPCs from attacking the bots:

Syntax: Select all

# ../silent_hill/silent_hill.py (npc edition)

# Python
import random

# Source.Python
from colors import Color
from commands.server import ServerCommand
from core import PLATFORM, echo_console
from engines.precache import Model
from engines.server import server
from engines.sound import Sound
from engines.trace import ContentMasks, engine_trace, GameTrace, Ray
from entities.constants import (EntityEffects, RenderMode, RenderEffects,
MoveType, WORLD_ENTITY_INDEX)
from entities.entity import Entity
from entities.helpers import index_from_edict, pointer_from_edict
from events import Event
from filters.entities import EntityIter
from listeners import (OnEntityCreated, OnEntityDeleted, OnLevelInit,
OnLevelEnd, OnClientActive, OnClientDisconnect)
from listeners.tick import Delay, Repeat
from mathlib import NULL_VECTOR, Vector, QAngle
from memory import Convention, DataType, NULL, find_binary
from memory.hooks import PreHook
from players import PlayerGenerator
from players.entity import Player
from stringtables import string_tables


# NOTE: This plugin was designed to work with the default settings for
# 'SIREN_SOUND' and 'SIREN_TIME'. Modifying these values might degrade your
# experience.


# Sound that plays during the siren phase - before the fog sets in.
# Default: 'ambient/alarms/citadel_alert_loop2.wav'
SIREN_SOUND = Sound('ambient/alarms/citadel_alert_loop2.wav')
# How long should the siren phase last? (in seconds)
# Default: 26
SIREN_TIME = 26


# How long until the apocalypse starts again? (in seconds)
INTERVALS = (30, 45, 60, 90)
# Seconds until the thick Silent Hill-like fog starts fading away.
FOG_FADE_DELAY = 60
# How long should the fading of the fog take? (in seconds)
FOG_FADE_TIME = 60


# Should NPCs spawn during the thick fog phase? (True/False)
NPCS_ENABLED = True
# Maximum number of NPCs at any given time.
NPCS_MAX = 10
# Should NPCs ignore bots? (True/False)
NPC_IGNORE_BOTS = True
# NPCs that can spawn. Be sure to properly configure the needed convars. These
# should be located in your server.cfg file. (e.g. sk_manhack_health,
# sk_manhack_damage, sk_zombie_health, sk_zombie_dmg_one_slash, and so on)
NPCS = (
'npc_zombie',
'npc_manhack',
'npc_headcrab',
'npc_antlion'
)

# If you wish to change models for certain NPCs, add them here.
NPC_MODELS = {
# Example code for changing models:
# The regular zombie will randomly pick between the 3 models.
'npc_zombie': (
Model('models/zombie/fast.mdl'),
Model('models/zombie/poison.mdl'),
Model('models/zombie/classic.mdl')
),
# The regular headcrab will appear as a fast headcrab.
'npc_headcrab': (Model('models/headcrab.mdl'),)
}

# For how many seconds should 'npc_manhack' entities glow after spawning?
# (setting this to 0 disables the glow)
MANHACK_GLOW_DURATION = 2


FOG_COLOR = Color(185, 185, 185)
FLASH_COLOR = Color(255, 0, 0, 150)
FLASH_COLOR_END = Color(255, 0, 0, 255)


# Sprite used for tinting the player's screen.
SCREEN_SPRITE = Model('sprites/white.vmt')
SCREEN_SPRITE_OFFSET = Vector(10, 0, 0)


# Offset for NPC spawn positions.
NPC_ORIGIN_OFFSET = Vector(0, 0, 32)
NPC_ORIGIN_MINS = Vector(-64, -64, 0)
NPC_ORIGIN_MAXS = Vector(64, 64, 64)


# Dictionary used to keep track of 'env_sprite' entities we'll be using.
_black_screens = {}


# =============================================================================
# >> EVENTS AND LISTENERS
# =============================================================================
def load():
"""Called when the plugin gets loaded."""
# Are there any players on the server?
if server.num_players > 0:
dark_times.initialize()


def unload():
"""Called when the plugin gets unloaded."""
dark_times.stop(pause_init=False)
dark_times.remove_all_npcs()

# Remove any leftover player entities.
for edict in PlayerGenerator():
PlayerSH(index_from_edict(edict)).remove_black_screen()


@OnLevelEnd
def on_level_end():
"""Called when the map starts changing."""
dark_times.stop()
# Remove old data (spawn points, npc indexes).
dark_times.clean_up_data()


@Event('round_start')
def round_start(event):
"""Called when a new round starts."""
dark_times.stop()
dark_times.initialize()


@OnLevelInit
def on_level_init(map_name):
"""Called when the new map is done loading."""
dark_times.initialize()


@OnClientActive
def on_client_active(index):
"""Called when a player fully connects to the server."""
# Is this the first player to join the server? (server was empty)
if server.num_players == 1:
dark_times.initialize()


@OnClientDisconnect
def on_client_disconnect(index):
"""Called when a player leaves the server."""
# Delay the call by a single frame - otherwise we might get incorrect info.
Delay(0, _on_client_disconnect)


def _on_client_disconnect():
# Did the last player just leave the server? (server is now empty)
if server.num_players == 0:
dark_times.stop()


@OnEntityCreated
def on_entity_created(base_entity):
"""Called when an entity gets created/spawned."""
try:
index = base_entity.index
except ValueError:
# Not a networked entity.
return

# Did a headcrab just spawn?
if 'npc_headcrab' in base_entity.classname:
npc = NPC(index)
npc.delay(0, npc.check_headcrab)


@OnEntityDeleted
def on_entity_deleted(base_entity):
"""Called when an entity gets deleted."""
try:
index = base_entity.index
except ValueError:
return

try:
# Was this one of our 'env_sprite' entities?
player_index = _black_screens.pop(index)
# Remove the instance reference from the player.
PlayerSH(player_index).black_screen = None
except (KeyError, ValueError):
pass

try:
# Was this an NPC we spawned?
dark_times.npc_indexes.remove(index)
except KeyError:
pass


@Event('entity_killed')
def npc_killed(event):
"""Called when an entity gets killed."""
index = event['entindex_killed']
# Is this one of our NPCs?
if index in dark_times.npc_indexes:
NPC(index).dissolve()


# =============================================================================
# >> PLAYER STUFF
# =============================================================================
class PlayerSH(Player):
"""Modified Player class."""

def __init__(self, index, caching=True):
"""Initializes the object."""
super().__init__(index, caching)
self.black_screen = None
self.target_name = f'player_{self.userid}'

@property
def viewmodel(self):
"""Returns the Entity instance of the player's viewmodel."""
return Entity.from_inthandle(self.get_property_int('m_hViewModel'))

def darken_view(self, amount):
"""Lowers the brightness of the player's screen."""
if self.black_screen is None:
self.black_screen = create_sprite(
origin=NULL_VECTOR, scale=30.0, model=SCREEN_SPRITE)

self.black_screen.set_parent(self.viewmodel, -1)
self.black_screen.teleport(SCREEN_SPRITE_OFFSET)

# Create a sprite:player reference for later use.
_black_screens[self.black_screen.index] = self.index

# Change the alpha/transparency of the 'env_sprite'.
self.black_screen.set_network_property_int('m_nBrightness', amount)

def remove_black_screen(self):
"""Removes the 'env_sprite' used for tinting the player's screen."""
try:
self.black_screen.remove()
except AttributeError:
return

self.black_screen = None


# =============================================================================
# >> DARK TIMES
# =============================================================================
class DarkTimes:
"""Class used to start and stop the apocalypse.

Attributes:
current_darkness (int): Level of darkness used for darkening players'
screens.
in_progress (bool): Is the apocalypse currently happening?
npc_indexes (set of int): Contains indexes of NPCs spawned during the
thick fog phase.
flash_think (Repeat): Instance of Repeat() used for looping the
`_flash_think()` function.
darken_think (Repeat): Instance of Repeat() used for looping the
`_darken_think()` function.
gather_data_think (Repeat): Instance of Repeat() used for looping the
`_gather_data_think()` function.
old_fog_values (tuple): Tuple that holds values of the previous fog.
_fog (Entity): Entity instance of the 'env_fog_controller' we'll be
using.
_push (Entity): Entity instance of the 'point_push' entity.
_delays (dict of Delay): Dictionary that holds any Delay() instances
we might be using.
_saved_time (float): Remaining time from the previous initialization.
_valid_npc_origins (list of Vector): List containing spawn positions
for NPCs.
"""

def __init__(self):
"""Initializes the object."""
self.current_darkness = 0
self.in_progress = False
self.npc_indexes = set()

self.flash_think = Repeat(self._flash_think)
self.darken_think = Repeat(self._darken_think)
self.gather_data_think = Repeat(self._gather_data_think)
self.spawn_npcs_think = Repeat(self._spawn_npcs_think)

self.old_fog_values = None
self._fog = None
self._push = None
self._delays = {}
self._saved_time = None
self._valid_npc_origins = []

def initialize(self, instant=False):
"""Starts the apocalypse after a randomly chosen delay."""
# Don't go further if the apocalypse is already happening.
if self.in_progress:
return

try:
self._delays['init'].cancel()
except (KeyError, ValueError):
pass

# Are we trying to instantly start the apocalypse?
if instant:
self.begin()
else:
self._delays['init'] = Delay(
# Resume the time to start from the previous initialization if
# there is one, otherwise pick a random time.
self._saved_time if self._saved_time else random.choice(
INTERVALS), self.begin)

def begin(self):
"""Starts the apocalypse."""
self.in_progress = True
self.current_darkness = 0
self._saved_time = None

try:
self._delays['init'].cancel()
except (KeyError, ValueError):
pass

SIREN_SOUND.play()
# Sync the red flashes with the siren sound - every time the players
# hear the siren, their screen will be flashed red.
# NOTE: This was synced with the default 'SIREN_SOUND'.
self.flash_think.start(
interval=6.5, limit=SIREN_TIME / 6.5, execute_on_start=True)
# Start lowering the brightness.
self.darken_think.start(interval=0.5, limit=40, execute_on_start=True)
# Look for valid spawn positions for NPCs during the siren phase.
self.gather_origin_data()

# Change to a thick fog similar to the one from Silent Hill.
# (credit: killer89 - https://gamebanana.com/prefabs/1308 )
self._delays['final_flash'] = Delay(
SIREN_TIME, self.change_fog, (FOG_COLOR, FOG_COLOR, 0, 620))
# Start changing the fog back to normal.
self._delays['restoration'] = Delay(
SIREN_TIME + FOG_FADE_DELAY, self.restore_fog_smooth, (
FOG_FADE_TIME,))

def stop(self, pause_init=True):
"""Stops the apocalypse and restores everything back to normal."""
SIREN_SOUND.stop()

# Stop the looping functions.
self.flash_think.stop()
self.darken_think.stop()
self.gather_data_think.stop()
self.spawn_npcs_think.stop()

if pause_init:
try:
# If we're stopping the apocalypse before it had a chance to
# begin, save the remaining time - so we can resume it later.
self._saved_time = self._delays['init'].time_remaining
except KeyError:
pass

# Cancel all delays.
for delay in self._delays.values():
try:
delay.cancel()
except ValueError:
continue

# Set the brightness back to normal levels.
for edict in PlayerGenerator():
PlayerSH(index_from_edict(edict)).darken_view(0)

self.restore_fog()
self.in_progress = False
self._fog = None

try:
self._push.remove()
except AttributeError:
pass

self._push = None

def clean_up_data(self):
"""Removes data that's no longer needed/valid."""
self._valid_npc_origins.clear()
self.npc_indexes.clear()

def remove_all_npcs(self):
"""Removes all currently active NPCs."""
for index in self.npc_indexes.copy():
NPC(index).remove()

def on_completed(self):
"""Called when the fog finally settles back to the default values."""
self.in_progress = False
# Prepare for the next apocalypse.
self.initialize()

def push_away_from(self, origin, radius, magnitude):
"""Pushes all movable entities at the specified origin.

Args:
origin (Vector): Point from which to push away entities from.
radius (float): Maximum distance an entity can be from the `origin`
and still be pushed away.
magnitude (float): Power/strength of the push.
"""
if self._push is None:
push = Entity.create('point_push')
# Set a couple of spawn flags.
# 8: Push players
# 16: Push physics
push.spawn_flags = 8 + 16
push.spawn()
self._push = push

self._push.origin = origin
self._push.set_property_float('m_flRadius', radius)
self._push.set_property_float('m_flMagnitude', magnitude)
self._push.call_input('Enable')
self._push.delay(0.15, self._push.call_input, ('Disable',))

def gather_origin_data(self):
"""Starts gathering valid spawn positions for NPCs."""
# Have we gathered a decent amount of spawn positions?
if len(self._valid_npc_origins) >= 32:
return

self.gather_data_think.start(
interval=2, limit=SIREN_TIME / 2, execute_on_start=True)

def _gather_data_think(self):
for edict in PlayerGenerator():
player = PlayerSH(index_from_edict(edict))

# Is this player dead?
if player.dead:
continue

new_origin = player.origin + NPC_ORIGIN_OFFSET
# Let's see if there's enough room for NPCs to spawn here.
enough_space = NPC.check_space(
origin=new_origin,
mins=NPC_ORIGIN_MINS,
maxs=NPC_ORIGIN_MAXS
)

if not enough_space:
continue

# Go through previously added spawn positions.
for origin in self._valid_npc_origins:
# Is the new position too close to this one?
if origin.get_distance(new_origin) <= 256:
# No need to keep looking, stop the loop.
break
else:
# The new position is far enough, add it to the list.
self._valid_npc_origins.append(new_origin)

def _flash_think(self):
UTIL_ScreenFadeAll(FLASH_COLOR, 0.5, 0.25, 1)

def _darken_think(self):
# Increase the darkness.
self.current_darkness += 5

# Reduce the brightness for each player on the server.
for edict in PlayerGenerator():
PlayerSH(index_from_edict(edict)).darken_view(
self.current_darkness)

def _spawn_npcs_think(self):
# Have we hit the NPC limit?
if len(self.npc_indexes) >= NPCS_MAX:
return

try:
origin = random.choice(self._valid_npc_origins)
except IndexError:
# Missing data for NPC spawn points.
return

# Push away any entities (props, players, npcs) from the spawn point.
self.push_away_from(origin, 200, 500)
# Spawn the NPC after a short delay.
self._delays['npc_spawn'] = Delay(0.15, self._spawn_npc, (origin,))

def _spawn_npc(self, origin):
# Pick an NPC to spawn.
npc = NPC.create(npc_name=random.choice(NPCS), origin=origin)

try:
# Try to add the NPC's index to the set.
self.npc_indexes.add(npc.index)
except AttributeError:
# The NPC wasn't properly created - skipping this wave.
return

# Move the NPC slightly in a random direction after spawning.
npc.base_velocity = get_random_direction() * 180

def get_fog_instance(self):
"""Returns an Entity instance of an 'env_fog_controller'."""
if self._fog is not None:
return self._fog

old_fog = Entity.find('env_fog_controller')
# Does an 'env_fog_controller' already exist on this map?
if old_fog:
# Store the old values for later use.
self.old_fog_values = (
old_fog.get_property_color('m_fog.colorPrimary'),
old_fog.get_property_color('m_fog.colorSecondary'),
old_fog.fog_start,
old_fog.fog_end
)

# We'll use that one for our fog shenanigans.
self._fog = old_fog
return self._fog

# Guess we need to make a new one.
new_fog = Entity.create('env_fog_controller')
new_fog.fog_enable = True
new_fog.fog_blend = True
new_fog.fog_max_density = 1.0
new_fog.spawn_flags = 1
new_fog.spawn()

new_fog.target_name = 'silent_hill_fog'
# Fix for maps without fog.
for edict in PlayerGenerator():
PlayerSH(index_from_edict(edict)).call_input(
'SetFogController', new_fog.target_name)

self._fog = new_fog
return self._fog

def remove_fog(self):
"""Removes the stored 'env_fog_controller' entity.

Note:
This is only used if we're making our own 'env_fog_controller'.
"""
try:
self._fog.remove()
except AttributeError:
return

self._fog = None

def change_fog(self, color1, color2, start, end, final_flash=True):
"""Changes the fog visuals.

Args:
color1 (Color): Primary color of the fog.
color2 (Color): Secondary color of the fog.
start (float): Distance at which the fog begins.
end (float): Distance at which the fog is at its maximum.
final_flash (bool): Is this the final red flash?
"""
fog = self.get_fog_instance()
fog.set_color(color1)
fog.set_color_secondary(color2)
fog.set_start_dist(start)
fog.set_end_dist(end)

# Is this the final flash?
if final_flash:
# Add a stronger flash to mask the changes in fog and screen
# brightness.
UTIL_ScreenFadeAll(FLASH_COLOR_END, 1, 0.5, 1)

for edict in PlayerGenerator():
PlayerSH(index_from_edict(edict)).darken_view(0)

# Should we start spawning NPCs?
if NPCS_ENABLED:
# Spawn them only during the thick fog phase.
self.spawn_npcs_think.start(
interval=3, limit=FOG_FADE_DELAY / 3)

def restore_fog(self):
"""Restores the fog back to normal."""
if self._fog is None:
return

# Was the map without fog?
if self.old_fog_values is None:
self.remove_fog()
return

self.change_fog(*self.old_fog_values, False)

def restore_fog_smooth(self, duration):
"""Smoothly restores the fog back to normal over the given duration."""
if self._fog is None:
return

should_remove = False
old_values = self.old_fog_values
# Was the map missing an 'env_fog_controller' entity?
if old_values is None:
should_remove = True
# Just increase the 'start' and 'end' values for the fog before
# removing it - making the transition semi-smooth.
old_values = (FOG_COLOR, FOG_COLOR, 512, 14000)

self._fog.set_color_lerp_to(old_values[0])
self._fog.set_color_secondary_lerp_to(old_values[1])
self._fog.set_start_dist_lerp_to(old_values[2])
self._fog.set_end_dist_lerp_to(old_values[3])
# Add 0.001 to the transition duration to avoid flashes caused by the
# fog bouncing back (engine quirk) before being set in place.
self._fog.fog_lerp_time = duration + 0.001
self._fog.start_fog_transition()

# If we created our own fog, we need to remove it.
if should_remove:
self._delays['ending'] = self._fog.delay(duration, self.remove_fog)
else:
# Make sure the fog values stay put after the transition.
self._delays['ending'] = self._fog.delay(
duration, self.change_fog, (*self.old_fog_values, False))

# The cycle is complete - let's do that again!
self._delays['restart'] = Delay(duration, self.on_completed)


dark_times = DarkTimes()


# =============================================================================
# >> UTIL_SCREENFADEALL - https://git.io/JJXoe
# =============================================================================
server_binary = find_binary('server')


if PLATFORM == 'windows':
identifier_screen = b'\x55\x8B\xEC\xD9\x45\x10\x8D\x45\xF4'
else:
identifier_screen = '_Z18UTIL_ScreenFadeAllRK9color32_sffi'


UTIL_ScreenFadeAll = server_binary[identifier_screen].make_function(
Convention.CDECL,
(DataType.POINTER, DataType.FLOAT, DataType.FLOAT, DataType.INT),
DataType.VOID
)


# =============================================================================
# >> UTIL_GETLOCALPLAYER FIX - (thank you Ayuto)
# viewtopic.php?f=20&t=1907#p12247
# =============================================================================
if PLATFORM == 'windows':
identifier_local = \
b'\xA1\x2A\x2A\x2A\x2A\x8B\x2A\x2A\x83\x2A\x01\x7E\x03\x33\xC0\xC3'
else:
identifier_local = '_Z19UTIL_GetLocalPlayerv'


UTIL_GetLocalPlayer = server_binary[identifier_local].make_function(
Convention.CDECL,
[],
DataType.POINTER
)


@PreHook(UTIL_GetLocalPlayer)
def get_local_player_pre(stack_data):
"""Called when the engine tries to get the local Player object.

This function was designed for single-player and should NOT be called in
multi-player games unless you want the server to crash.
"""
for edict in PlayerGenerator():
try:
return pointer_from_edict(edict)
except ValueError:
pass

return NULL


# =============================================================================
# >> CNPC_BaseZombie::ReleaseHeadcrab() - https://git.io/JUou2
# =============================================================================
if PLATFORM == 'windows':
identifier_release = \
b'\x55\x8B\xEC\x83\xEC\x18\x53\x56\x57\x8B\xF9\x8B\x4D\x08\x80\xBF\x60'
else:
identifier_release = \
'_ZN15CNPC_BaseZombie15ReleaseHeadcrabERK6VectorS2_bbb'


ReleaseHeadcrab = server_binary[identifier_release].make_function(
Convention.THISCALL,
(
DataType.POINTER, DataType.POINTER, DataType.POINTER, DataType.BOOL,
DataType.BOOL, DataType.BOOL),
DataType.VOID
)


@PreHook(ReleaseHeadcrab)
def release_headcrab_pre(stack_data):
"""Called when a zombie is about to release a headcrab."""
# Is the zombie trying to release a headcrab ragdoll?
if stack_data[5]:
# Block the release - stop the ragdoll from spawning.
return False


# =============================================================================
# >> ENV_SPRITE
# =============================================================================
def create_sprite(origin, scale, model):
"""Creates an 'env_sprite' entity.

Args:
origin (Vector): Spawn position of the 'env_sprite'.
scale (float): Size of the sprite (max size: 64.0).
model (Model): Appearance of the sprite.
"""
sprite = Entity.create('env_sprite')
sprite.model = model
sprite.origin = origin
sprite.set_key_value_float('scale', scale)
sprite.set_key_value_bool('disablereceiveshadows', True)
sprite.set_key_value_float('HDRColorScale', 0)
sprite.render_amt = 1
sprite.render_mode = RenderMode.TRANS_COLOR
sprite.set_key_value_string('rendercolor', '0 0 0')
sprite.render_fx = RenderEffects.NONE
sprite.spawn_flags = 1
sprite.spawn()
return sprite


# =============================================================================
# >> NPC
# =============================================================================
class NPC(Entity):
"""Class used to create and interact with NPC entities."""
effect_name = 'vortigaunt_hand_glow'

def __init__(self, index, caching=True):
"""Initializes the object."""
super().__init__(index, caching)
# Give the NPC a name.
self.target_name = f'sh_npc_{self.inthandle}'

@staticmethod
def check_space(origin, mins, maxs):
"""Checks if there is enough space at the given origin."""
trace = GameTrace()
engine_trace.clip_ray_to_entity(
Ray(origin, origin, mins, maxs),
ContentMasks.ALL,
Entity(WORLD_ENTITY_INDEX),
trace
)

return not trace.did_hit()

@classmethod
def create(cls, npc_name, origin, seek_after_spawn=True):
"""Creates the NPC entity.

Args:
npc_name (str): Name of the NPC to spawn. (e.g. 'npc_zombie')
origin (Vector): Spawn position of the NPC.
seek_after_spawn (bool): Should the NPC start looking for players
after spawning?
"""
try:
npc = super().create(npc_name)
except ValueError:
# Invalid entity class name.
echo_console(f'ERROR! Attempted to create invalid NPC: {npc_name}')
return

npc.origin = origin
# Make the NPC fully transparent.
npc.render_amt = 1
# Slowly fade it into existence.
npc.set_key_value_int('renderfx', 7)
# Set a couple of spawn flags.
# 2: Gag (No IDLE sounds until angry)
# 256: Long Visibility/Shoot
npc.spawn_flags = 2 + 256
npc.spawn()
npc.become_hostile()

try:
# Are there any alternative models for this NPC?
npc.model = random.choice(NPC_MODELS[npc_name])
except KeyError:
# Nope, moving on..
pass

if seek_after_spawn and npc_name != 'npc_antlion':
# Start going towards a random player after a short delay.
# We need to do this so the NPC doesn't just stand still once it
# spawns. This way it won't block the spawn point.
npc.delay(0.2, npc.seek_random_player)

# Is this an 'npc_manhack' and are glows enabled?
if 'manhack' in npc_name and MANHACK_GLOW_DURATION > 0:
npc.create_spawn_particle(MANHACK_GLOW_DURATION)

if NPC_IGNORE_BOTS:
npc.ignore_bots()

return npc

def create_spawn_particle(self, life_time):
"""Creates and parents an 'info_particle_system' to the NPC."""
particle = Entity.create('info_particle_system')
particle.origin = self.origin
particle.effect_name = NPC.effect_name
particle.effect_index = string_tables.ParticleEffectNames.add_string(
NPC.effect_name)
particle.start()

# Parent the particle to the NPC.
particle.set_parent(self)
# Stop the effect before removing it. This will prevent the effect
# from stopping (detaching from the NPC) in place and looking weird.
particle.delay(life_time, particle.stop)
# Remove the particle after a bit of a delay.
particle.delay(life_time + 2, particle.remove)

def become_hostile(self):
"""Makes the NPC hate the players."""
self.call_input('SetRelationship', 'player D_HT 98')

def ignore_bots(self):
"""Makes the NPC neutral towards bots, if there are any."""
for edict in PlayerGenerator():
player = PlayerSH(index_from_edict(edict))

if player.is_bot():
self.call_input(
'SetRelationship', f'{player.target_name} D_NU 99')

def seek_random_player(self):
"""Starts heading towards a randomly selected player."""
targets = []
for edict in PlayerGenerator():
player = PlayerSH(index_from_edict(edict))

# Is this player dead?
if player.dead:
# Skip them.
continue

# Is this a bot and should we ignore it?
if player.is_bot() and NPC_IGNORE_BOTS:
continue

targets.append(player.target_name)

# Is the list empty?
if not targets:
return

# We need an 'aiscripted_schedule' entity to make the NPC do things.
schedule = Entity.create('aiscripted_schedule')
# Set which entity we'd like to command.
schedule.set_key_value_string('m_iszEntity', self.target_name)
# Set their current state to 'Idle'.
# (0: None, 1: Idle, 2: Alert, 3: Combat)
schedule.set_key_value_int('forcestate', 1)
# Make the NPC set the target as their enemy and start going towards
# them. Here are all the options for this keyvalue:
# 0: <None>
# 1: Walk to Goal Entity
# 2: Run to Goal Entity
# 3: Set enemy to Goal Entity
# 4: Walk Goal Path
# 5: Run Goal Path
# 6: Set enemy to Goal Entity AND Run to Goal Entity
schedule.set_key_value_int('schedule', 6)
# In case the NPC takes damage, stop the schedule.
# (0: General, 1: Damage or death, 2: Death)
schedule.set_key_value_int('interruptability', 1)
# Pick a random player to be the enemy.
schedule.set_key_value_string('goalent', random.choice(targets))
schedule.spawn()
schedule.call_input('StartSchedule')
schedule.delay(5, schedule.remove)

def dissolve(self):
"""Removes the ragdoll of the NPC."""
dissolver = Entity.find_or_create('env_entity_dissolver')
dissolver.dissolve_type = 0
dissolver.dissolve(self.target_name)

def check_headcrab(self):
"""Checks if the headcrab was previously attached to a zombie."""
# Does this headcrab lack an owner? (invalid inthandle)
if self.owner_handle == -1:
# No need to go further.
return

self.become_hostile()
self.delay(0.1, self.seek_random_player)
dark_times.npc_indexes.add(self.index)


# =============================================================================
# >> HELPERS
# =============================================================================
def get_random_direction():
"""Returns a random directional vector without the Z axis (up/down)."""
direction = Vector()
QAngle(0, random.randint(0, 360), 0).get_angle_vectors(forward=direction)
return direction.normalized()


# =============================================================================
# >> SERVER COMMANDS
# =============================================================================
@ServerCommand('force_dark_times')
def force_dark_times_cmd(command):
dark_times.initialize(instant=True)
This behavior can be toggled with the NPC_IGNORE_BOTS value.
ImageImageImageImageImage
User avatar
PEACE
Junior Member
Posts: 23
Joined: Mon Oct 12, 2020 1:13 pm

Re: [HL2:DM] Little Silent Hill

Postby PEACE » Tue Nov 24, 2020 1:15 am

This is fine and seems to work fine for me , either way would have been good since we use custom guns and they dont they are at a disadvantage to start with LOL , I just hope Valve doesnt break things like they use to all the time because they lost many servers over that in the past .
As always VinciT thank you for you wonderful work much appreciated :)

/PEACE

Return to “Plugin Requests”

Who is online

Users browsing this forum: No registered users and 2 guests