[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
Painkiller
Senior Member
Posts: 730
Joined: Sun Mar 01, 2015 8:09 am
Location: Germany
Contact:

[HL2:DM] Little Silent Hill

Postby Painkiller » Sun Jul 26, 2020 9:50 am

Could someone write this?

The siren goes on. It's getting darker.
Dark and everything flashes red.
When it gets light again it is foggy.

The siren and darker can pass randomly in indicated intervals.

Thanks in Advance
User avatar
Painkiller
Senior Member
Posts: 730
Joined: Sun Mar 01, 2015 8:09 am
Location: Germany
Contact:

Re: [HL2:DM] Little Silent Hill

Postby Painkiller » Wed Aug 26, 2020 8:32 am

push
User avatar
VinciT
Senior Member
Posts: 331
Joined: Thu Dec 18, 2014 2:41 am

Re: [HL2:DM] Little Silent Hill

Postby VinciT » Fri Aug 28, 2020 3:06 am

Here's my interpretation of your request:


Syntax: Select all

# ../silent_hill/silent_hill.py

# Python
import random

# Source.Python
from colors import Color
from commands.server import ServerCommand
from core import PLATFORM
from engines.precache import Model
from engines.server import server, server_game_dll
from engines.sound import Sound
from entities.constants import RenderMode, RenderEffects
from entities.entity import Entity
from entities.helpers import index_from_edict
from events import Event
from listeners import OnEntityDeleted, OnLevelInit, OnLevelEnd
from listeners.tick import Delay, Repeat
from mathlib import NULL_VECTOR, Vector
from memory import Convention, DataType, find_binary, get_virtual_function
from memory.hooks import PreHook
from players import PlayerGenerator
from players.entity import Player


# 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


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


SIREN_SOUND = Sound('ambient/alarms/citadel_alert_loop2.wav')


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

# 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)

# 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()


@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()


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

try:
# Was this one of our 'env_sprite' entities?
player_index = _black_screens.pop(index)
except KeyError:
return

# Remove the instance reference from the player.
PlayerSH(player_index).black_screen = None


@PreHook(get_virtual_function(server_game_dll, 'SetServerHibernation'))
def set_server_hibernation_pre(stack_data):
"""Called when the last player leaves, or the first player joins."""
# Did the last player just leave (server is being put into hibernation)?
if stack_data[1]:
dark_times.stop()
# Or did the first player just join?
else:
dark_times.initialize()


# =============================================================================
# >> 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

@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 the apocalypse.

Attributes:
current_darkness (int): Level of darkness used for darkening players'
screens.
in_progress (bool): Is the apocalypse currently happening?
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.
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.
_delays (dict of Delay): Dictionary that holds any Delay() instances
we might be using.
_saved_time (float): Remaining time from the previous initialization.
"""

def __init__(self):
"""Initializes the object."""
self.current_darkness = 0
self.in_progress = False
self.flash_think = Repeat(self._flash_think)
self.darken_think = Repeat(self._darken_think)
self.old_fog_values = None
self._fog = None
self._delays = {}
self._saved_time = None

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.
self.flash_think.start(interval=6.5, limit=4, execute_on_start=True)
# Start lowering the brightness.
self.darken_think.start(interval=0.5, limit=40, execute_on_start=True)

# Change to a thick fog similar to the one from Silent Hill.
# (credit: killer89 - https://gamebanana.com/prefabs/1308 )
self._delays['final_flash'] = Delay(
26, self.change_fog, (FOG_COLOR, FOG_COLOR, 0, 620))
# Start changing the fog back to normal.
self._delays['restoration'] = Delay(
26 + 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()

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

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 _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 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)

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 = b'\x55\x8B\xEC\xD9\x45\x10\x8D\x45\xF4'
else:
identifier = '_Z18UTIL_ScreenFadeAllRK9color32_sffi'


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


# =============================================================================
# >> 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


# =============================================================================
# >> SERVER COMMANDS
# =============================================================================
@ServerCommand('force_dark_times')
def force_dark_times_cmd(command):
dark_times.initialize(instant=True)
Last edited by VinciT on Mon Sep 07, 2020 3:53 am, edited 3 times in total.
ImageImageImageImageImage
User avatar
Painkiller
Senior Member
Posts: 730
Joined: Sun Mar 01, 2015 8:09 am
Location: Germany
Contact:

Re: [HL2:DM] Little Silent Hill

Postby Painkiller » Sat Aug 29, 2020 10:39 am

Perfect, that looks good i will test it the days and let you know.
User avatar
Painkiller
Senior Member
Posts: 730
Joined: Sun Mar 01, 2015 8:09 am
Location: Germany
Contact:

Re: [HL2:DM] Little Silent Hill

Postby Painkiller » Sun Aug 30, 2020 8:34 am

Well I have tested it, unfortunately it does not work for me. I have no sound and no fog.
There are no errors in the log.
I have changed the times, could that be the reason ?

Code: Select all

# How long until the apocalypse starts again (in seconds)?
INTERVALS = (300, 600, 900, 1200)
# 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


Code: Select all

2020-08-30 10:25:51 - sp   -   MESSAGE   [SP] Loading plugin 'silenthill'...
2020-08-30 10:25:51 - sp   -   MESSAGE   [SP] Successfully loaded plugin 'silenthill'.
User avatar
VinciT
Senior Member
Posts: 331
Joined: Thu Dec 18, 2014 2:41 am

Re: [HL2:DM] Little Silent Hill

Postby VinciT » Sun Aug 30, 2020 8:48 pm

The values you used should work fine. I think the issue might be the fact that the delay/timer to start the event gets reset whenever the map changes, a new round starts, as well as whenever the server becomes empty. And since you used values from 5 to 20 minutes, there's a high chance the delay is getting reset before anything actually happens.

Just to be sure, I've updated the code in my previous post to include a server command to force the event to start. Run force_dark_times either through rcon or the server console. Tell me if that works for you - if it does, I'll modify the plugin once more to include some sort of pausing/resuming mechanic for the delay.
ImageImageImageImageImage
User avatar
Painkiller
Senior Member
Posts: 730
Joined: Sun Mar 01, 2015 8:09 am
Location: Germany
Contact:

Re: [HL2:DM] Little Silent Hill

Postby Painkiller » Mon Aug 31, 2020 7:28 am

Apparently it is not recognized

Code: Select all

09:27:13 force_dark_times
09:27:13 Unknown command "force_dark_times"
09:27:46 force_dark_times 60
09:27:46 Unknown command "force_dark_times"
User avatar
VinciT
Senior Member
Posts: 331
Joined: Thu Dec 18, 2014 2:41 am

Re: [HL2:DM] Little Silent Hill

Postby VinciT » Mon Aug 31, 2020 5:29 pm

I know it's an odd question, but are you sure the plugin was loaded? :confused:
ImageImageImageImageImage
User avatar
Painkiller
Senior Member
Posts: 730
Joined: Sun Mar 01, 2015 8:09 am
Location: Germany
Contact:

Re: [HL2:DM] Little Silent Hill

Postby Painkiller » Mon Aug 31, 2020 8:00 pm

Painkiller wrote:Well I have tested it, unfortunately it does not work for me. I have no sound and no fog.
There are no errors in the log.
I have changed the times, could that be the reason ?

Code: Select all

# How long until the apocalypse starts again (in seconds)?
INTERVALS = (300, 600, 900, 1200)
# 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


Code: Select all

2020-08-30 10:25:51 - sp   -   MESSAGE   [SP] Loading plugin 'silenthill'...
2020-08-30 10:25:51 - sp   -   MESSAGE   [SP] Successfully loaded plugin 'silenthill'.


yes
User avatar
VinciT
Senior Member
Posts: 331
Joined: Thu Dec 18, 2014 2:41 am

Re: [HL2:DM] Little Silent Hill

Postby VinciT » Tue Sep 01, 2020 7:52 pm

That is so strange.. From what I can tell, that shouldn't be happening. It's as if you're still using the old plugin.
Either way, I've updated the code once again to include the pausing/resuming mechanic for the timer. Grab the latest plugin and tell me if it works.

You could also try using lower values as a test - just to make sure the plugin actually works.
ImageImageImageImageImage
User avatar
Painkiller
Senior Member
Posts: 730
Joined: Sun Mar 01, 2015 8:09 am
Location: Germany
Contact:

Re: [HL2:DM] Little Silent Hill

Postby Painkiller » Wed Sep 02, 2020 7:38 am

It is definitely loading. But I do not see any effects in the game.
I would like to give you the login from my server so you can see for yourself.

Please contact me as soon as you have time.

Code: Select all

2020-09-02 09:33:45 - sp   -   MESSAGE   [SP] Successfully unloaded plugin 'silenthill'.
2020-09-02 09:33:45 - sp   -   MESSAGE   [SP] Loading plugin 'silenthill'...
2020-09-02 09:33:45 - sp   -   MESSAGE   [SP] Successfully loaded plugin 'silenthill'.
User avatar
daren adler
Senior Member
Posts: 348
Joined: Sat May 18, 2019 7:42 pm

Re: [HL2:DM] Little Silent Hill

Postby daren adler » Fri Sep 04, 2020 2:19 am

Hello python scripters..I tryed the plugin and it works, GOOD job VinciT, My server is windows.Painkiller,, maybe thats why yours dont work. Idk tho, i am not the maker or even a maker at all,just guessing that. but If i remember right you are not a windows right?, anyway works for me(ps) i made sure i was updated also,,but tryed it before the new update and worked even then. Stay cool and VinciT again nice job. Hope you get it working painkiller.
User avatar
Painkiller
Senior Member
Posts: 730
Joined: Sun Mar 01, 2015 8:09 am
Location: Germany
Contact:

Re: [HL2:DM] Little Silent Hill

Postby Painkiller » Fri Sep 04, 2020 9:17 am

I would like to give them access to my server so you can see for yourself.
User avatar
VinciT
Senior Member
Posts: 331
Joined: Thu Dec 18, 2014 2:41 am

Re: [HL2:DM] Little Silent Hill

Postby VinciT » Sat Sep 05, 2020 4:46 pm

Painkiller wrote:I would like to give them access to my server so you can see for yourself.
Sorry, was a bit busy the past couple of days. Feel free to PM me the server details and I'll see if I can sort this out.

daren adler wrote:anyway works for me(ps) i made sure i was updated also,,but tryed it before the new update and worked even then. Stay cool and VinciT again nice job.
Thank you for the kind words! :grin:
ImageImageImageImageImage
User avatar
daren adler
Senior Member
Posts: 348
Joined: Sat May 18, 2019 7:42 pm

Re: [HL2:DM] Little Silent Hill

Postby daren adler » Sun Sep 06, 2020 12:49 am

I was thinking :confused: :confused: , I am trying to figure out a way so when the silent hill starts, npc will spawn. I am using a cheats plugin, and i know how to have npc spawn and work.

Here is the cfg of one of the npc's

sm_rcon sv_cheats 1
sk_manhack_health "100"
sk_manhack_melee_dmg "25"
sk_manhack_v2 "1"
ai_enable_fear_behavior "1"
npc_create npc_manhack
ent_fire npc_manhack setrelationship "player d_ht 99"
sm_rcon sv_cheats 0

Then when on server i can spawn a npc by using exec npc_manhack (bind u npc_zombie) and where i aim with pistol and hit u, it will spawn the npc.

What could i do so the npc only spawn when the silent hill starts??. Is there another plugin you know of to do that, or some kind of a trigger that would make it work with your plugin?. stay cool :cool: and have a great weekend!!!. In the movies/games, when the sounds stops the silent hill bad guys come out to kill. That is what i am trying to do.
User avatar
Painkiller
Senior Member
Posts: 730
Joined: Sun Mar 01, 2015 8:09 am
Location: Germany
Contact:

Re: [HL2:DM] Little Silent Hill

Postby Painkiller » Sun Sep 06, 2020 6:03 am

I have sent you a mail.
User avatar
Painkiller
Senior Member
Posts: 730
Joined: Sun Mar 01, 2015 8:09 am
Location: Germany
Contact:

Re: [HL2:DM] Little Silent Hill

Postby Painkiller » Sun Sep 06, 2020 6:13 am

daren adler wrote:I was thinking :confused: :confused: , I am trying to figure out a way so when the silent hill starts, npc will spawn. I am using a cheats plugin, and i know how to have npc spawn and work.

Here is the cfg of one of the npc's

sm_rcon sv_cheats 1
sk_manhack_health "100"
sk_manhack_melee_dmg "25"
sk_manhack_v2 "1"
ai_enable_fear_behavior "1"
npc_create npc_manhack
ent_fire npc_manhack setrelationship "player d_ht 99"
sm_rcon sv_cheats 0

Then when on server i can spawn a npc by using exec npc_manhack (bind u npc_zombie) and where i aim with pistol and hit u, it will spawn the npc.

What could i do so the npc only spawn when the silent hill starts??. Is there another plugin you know of to do that, or some kind of a trigger that would make it work with your plugin?. stay cool :cool: and have a great weekend!!!. In the movies/games, when the sounds stops the silent hill bad guys come out to kill. That is what i am trying to do.


Hello Daren try to do this with an exec.cfg (like nuke.cfg) with the zombie mod for HL2DM sourcemod plugin. If I remember correctly this mod is only for Windows.

(I hope you have saved it or I will look in my archives)
But I think the best partner would be: https://steamcommunity.com/id/4oM0/
User avatar
daren adler
Senior Member
Posts: 348
Joined: Sat May 18, 2019 7:42 pm

Re: [HL2:DM] Little Silent Hill

Postby daren adler » Sun Sep 06, 2020 6:41 am

"Yes"... your thought is right,,i do not have that no more PailKiller, If you could send me it or where to grab it again that would be a start. If i remember right, never could get that mod to work back then, but heck man i will give it a try again,,would be nice to have them come out after sound and silent hill starts. makes it more like the real silent hill. I am using a sound from a airraid that sounds real close to the game/movie sound.


(NEW THOUGHT), I have a skin i use for my hl2dm hurricane bots,,they are the hl2 zombies skins on combines. I could use them to spawn when the silent hill starts....i will try and see what i can do with it that way,,might work, might not. Would make it eaiser and less stuff to have to come up with!!!!.
User avatar
VinciT
Senior Member
Posts: 331
Joined: Thu Dec 18, 2014 2:41 am

Re: [HL2:DM] Little Silent Hill

Postby VinciT » Mon Sep 07, 2020 4:01 am

daren adler wrote:What could i do so the npc only spawn when the silent hill starts??. Is there another plugin you know of to do that, or some kind of a trigger that would make it work with your plugin?. stay cool :cool: and have a great weekend!!!. In the movies/games, when the sounds stops the silent hill bad guys come out to kill. That is what i am trying to do.
Here's a quick attempt at adding manhacks:

Syntax: Select all

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

# Python
import random

# Source.Python
from colors import Color
from commands.server import ServerCommand
from core import PLATFORM
from cvars import ConVar
from engines.precache import Model
from engines.server import server, server_game_dll
from engines.sound import Sound
from entities.constants import RenderMode, RenderEffects
from entities.entity import BaseEntity, Entity
from entities.helpers import index_from_edict
from events import Event
from listeners import OnEntityDeleted, OnLevelInit, OnLevelEnd
from listeners.tick import Delay, Repeat
from mathlib import NULL_VECTOR, Vector
from memory import Convention, DataType, find_binary, get_virtual_function
from memory.hooks import PreHook
from players import PlayerGenerator
from players.entity import Player
from stringtables import string_tables


# 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 = 32

# Apply a glowing green effect that can be seen through the fog when the
# manhacks spawn? (True/False)
MANHACKS_SPAWN_EFFECT = False
# How much health should manhacks spawn with?
MANHACKS_HEALTH = 100
# How much damage should they deal?
MANHACKS_DAMAGE = 25


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


SIREN_SOUND = Sound('ambient/alarms/citadel_alert_loop2.wav')


# 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_SPAWN_VELOCITY = (-500, -250, 250, 500)


# 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."""
# Modify the manhack related convars.
Manhack.initialize_settings()
# 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()


@OnEntityDeleted
def on_entity_deleted(base_entity):
"""Called when an entity gets deleted."""
try:
index = base_entity.index
except ValueError:
# Not a networked entity.
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:
pass

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


@PreHook(get_virtual_function(server_game_dll, 'SetServerHibernation'))
def set_server_hibernation_pre(stack_data):
"""Called when the last player leaves, or the first player joins."""
# Did the last player just leave (server is being put into hibernation)?
if stack_data[1]:
dark_times.stop()
# Or did the first player just join?
else:
dark_times.initialize()


# =============================================================================
# >> 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

@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.
_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._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.
self.flash_think.start(interval=6.5, limit=4, 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(
26, self.change_fog, (FOG_COLOR, FOG_COLOR, 0, 620))
# Start changing the fog back to normal.
self._delays['restoration'] = Delay(
26 + 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

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():
BaseEntity(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 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=14, 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

# 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

manhack = Manhack.create(origin=random.choice(self._valid_npc_origins))
# Push the manhack in a random direction.
manhack.set_property_vector(
'm_vForceVelocity', Vector(
random.choice(NPC_SPAWN_VELOCITY),
random.choice(NPC_SPAWN_VELOCITY),
500
))
# Add the manhack's index to the set.
self.npc_indexes.add(manhack.index)

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 = b'\x55\x8B\xEC\xD9\x45\x10\x8D\x45\xF4'
else:
identifier = '_Z18UTIL_ScreenFadeAllRK9color32_sffi'


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


# =============================================================================
# >> 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_MANHACK
# =============================================================================
class Manhack(Entity):
"""Class used to create and manipulate 'npc_manhack' entities."""
caching = True
effect_name = 'vortigaunt_hand_glow'
settings = {
'health': ConVar('sk_manhack_health'),
'damage': ConVar('sk_manhack_melee_dmg')
}

@staticmethod
def initialize_settings():
"""Changes the convars for 'npc_manhack' health and damage."""
Manhack.settings['health'].set_int(MANHACKS_HEALTH)
Manhack.settings['damage'].set_int(MANHACKS_DAMAGE)

@classmethod
def create(cls, origin):
"""Creates an 'npc_manhack' entity at the specified origin."""
manhack = cls(BaseEntity.create('npc_manhack').index)
manhack.origin = origin
# Make the manhack fully transparent.
manhack.render_amt = 1
# Slowly fade in the manhack.
manhack.set_key_value_int('renderfx', 7)
# Set a couple of spawn flags.
# 2: Gag (No IDLE sounds until angry)
# 256: Long Visibility/Shoot
manhack.spawn_flags = 2 + 256
manhack.spawn()

if MANHACKS_SPAWN_EFFECT:
manhack.create_spawn_particle()

# Make the manhack hate the players.
manhack.call_input('SetRelationship', 'player D_HT 99')
return manhack

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

# Parent the particle to the 'npc_manhack'.
particle.set_parent(self)
# Remove the particle after a second.
particle.delay(1, particle.remove)


# =============================================================================
# >> SERVER COMMANDS
# =============================================================================
@ServerCommand('force_dark_times')
def force_dark_times_cmd(command):
dark_times.initialize(instant=True)
Manhacks will start spawning once the siren stops, and will continue to spawn until the fog starts to fade away. This can be easily expanded upon to include other types of NPCs as well. If anyone wishes to do so, this function is responsible for spawning NPCs during the fog phase:

Syntax: Select all

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

manhack = Manhack.create(origin=random.choice(self._valid_npc_origins))
# Push the manhack in a random direction.
manhack.set_property_vector(
'm_vForceVelocity', Vector(
random.choice(NPC_SPAWN_VELOCITY),
random.choice(NPC_SPAWN_VELOCITY),
500
))
# Add the manhack's index to the set.
self.npc_indexes.add(manhack.index)
Make sure to setup the convars for any NPCs you add either in autoexec/server.cfg or through code (see how I did it in the Manhack class).

Also, I've updated the plugin in the original post to include two bugfixes:
  • Server could crash after a map change due to me not removing the reference to the fog from the previous map.
  • Apocalypse not starting if mp_restartgame was used.
Last edited by VinciT on Mon Sep 07, 2020 9:06 pm, edited 1 time in total.
ImageImageImageImageImage
User avatar
daren adler
Senior Member
Posts: 348
Joined: Sat May 18, 2019 7:42 pm

Re: [HL2:DM] Little Silent Hill

Postby daren adler » Mon Sep 07, 2020 7:20 am

Will give that a try. THANK YOU VinciT :cool: :cool: ...Ok i tryed it and ........Its works great, Thank you Great job :smile: :smile: :smile:
PS-- Could you add the zombie for me please? i tryed and messed it all up...im not so good as ur.
Last edited by daren adler on Mon Sep 07, 2020 4:02 pm, edited 2 times in total.

Return to “Plugin Requests”

Who is online

Users browsing this forum: No registered users and 3 guests