Skip to content

Instantly share code, notes, and snippets.

@aemarkov
Created June 16, 2019 22:33
Show Gist options
  • Save aemarkov/5b5352d3b8deed87023996c28186081b to your computer and use it in GitHub Desktop.
Save aemarkov/5b5352d3b8deed87023996c28186081b to your computer and use it in GitHub Desktop.
E2 script for Garry's Mod killer bath
# This is an advanced KILLER BATH
# Features:
# - precise player targeting with PID-controller
# - rotation control
# - OPTIONAL: teleport though obstacles (using ranger)
# - OPTIONAL: explode when close to the player
# (to kill players with no-clip)
@name killer_bath
@inputs
@outputs Detonate Arm TeleportPos:vector Jump Freeze
@persist MovePID:table RotPID:table LastTarget:entity PrevDist MyEntities:array FreezeTimeout
@trigger
interval(5)
Base = entity():isWeldedTo()
IS_TARGET_OWNER = 1 # Bath will target owner
IS_DETONATE = 0 # Bath will detonate when close to player
IS_TELEPORT = 0 # Bath will teleport through the obstacles
IS_BEHIND_THE_BACK = 0 # Bath will hide behind the player's back instead of killing him
IS_CRYING_ANGEL = 0 # Bath will freeze when any player look at it
ARM_DIST = 1000
DETONATE_DIST = 300
JUMP_MIN_DIST = 10
JUMP_MIN_DIST_ERROR = 40
BEHIND_THE_BACK_DIST = 100
MIN_ROTATE_DIST = 0
MAX_FREEZE_TIMEOUT = 30
####################### PID CONTROLLER #############################
# Add nessesary items to PID's table
function initPID(PID:table, P:number, I:number, D:number)
{
PID["PGain", number]=P
PID["IGain", number]=I
PID["DGain", number]=D
PID["IState", vector] = vec()
PID["DState", vector] = vec()
}
# Calculate given PID
function vector calcPID(Vec:vector, PID:table)
{
PTerm = PID["PGain", number] * Vec
IState = PID["IState", vector]
IMax = PID["IMax", number]
IState = IState + Vec
if(IState:length() > IMax)
{
IState = IState:normalized() * IMax
PID["IState", vector] = IState
}
ITerm = PID["IGain", number] * IState
DTerm = PID["DGain", number] * (Vec - PID["DState", vector])
PID["DState", vector] = Vec
return PTerm + IState + DTerm
}
####################################################################
# Find closest player
function entity findClosestPlayer()
{
Players = players()
MyPos = Base:pos()
ClosestPlayer = noentity()
MinDist = 99999
for(I = 1, Players:count())
{
Player = Players[I, entity]
PlayerPos = Player:pos()
Dist = (PlayerPos - MyPos):length()
NotOwner = (Player != owner()) || (IS_TARGET_OWNER == 1)
if(Dist < MinDist && Player:health() > 0 && NotOwner)
{
MinDist = Dist
ClosestPlayer = Player
}
}
return ClosestPlayer
}
# Find closest player and update target
function entity chooseTarget()
{
# Find player
ClosestPlayer = findClosestPlayer()
if(ClosestPlayer==noentity())
{
LastTarget = ClosestPlayer
return noentity()
}
if(ClosestPlayer != LastTarget)
{
print("New target: " + ClosestPlayer:name())
}
LastTarget = ClosestPlayer
return ClosestPlayer
}
####################################################################
# Apply force to move
function move(TargetPos:vector, LookToPos:vector)
{
#holoCreate(10, TargetPos)
Vec0 = TargetPos - Base:pos()
Base:applyForce(calcPID(Vec0, MovePID))
if((LookToPos - Base:pos()):length() > MIN_ROTATE_DIST)
{
Heading = Base:heading(LookToPos)
Angles = Base:angles()
RotErr = vec(-Base:angles():roll(), -Heading:pitch(), -Heading:yaw())
Base:applyTorque(calcPID(RotErr, RotPID))
}
}
# Check distance and detonate warhead
function detonate(Distance)
{
Arm = Distance < ARM_DIST
if(Distance < DETONATE_DIST && Detonate == 0)
{
Detonate = 1
}
else
{
Detonate = 0
}
}
# Teleport if can't reach target
function teleport(TargetPos:vector)
{
R = rangerOffset(Base:pos(), TargetPos)
#holoCreate(10, R:position())
DistToHit = R:distance()
DistError = Dist - DistToHit
Dist = (TargetPos - Base:pos()):length()
if(Jump == 0 && Dist > JUMP_MIN_DIST && DistError > JUMP_MIN_DIST_ERROR && !entity():isFrozen() && !Base:isFrozen())
{
TeleportPos = TargetPos
Jump = 1
print("Jump")
}
else
{
Jump = 0
}
#print(Dist, PrevDist-Dist)
#PrevDist = Dist
}
####################################################################
# Return entities welded to Base
function array get_my_entities()
{
I=1
MyEnts = array()
MyEnts:pushEntity(Base)
while(1)
{
Child = Base:isWeldedTo(I)
if(Child:model()=="") { break }
MyEnts:pushEntity(Child)
I = I+1
}
return MyEnts
}
# Is any player looking at this
function number is_looking_at()
{
Players = players()
for(I = 1, Players:count())
{
for(J = 1, MyEntities:count())
{
if(Players[I, entity]:aimEntity() == MyEntities[J, entity])
{
return 1
}
}
}
return 0
}
####################################################################
# Initialize all
function setup()
{
initPID(MovePID, 1000, 0, 10000)
initPID(RotPID, 5000, 0, 80000)
LastTarget = noentity()
MyEntities = get_my_entities()
rangerPersist(1)
rangerFilter(MyEntities)
}
# Run every cycle
function loop()
{
# Crying angel mode - freezes when any player look at
# Bath moving very fast, so I add some delay to simplify
# It will stay freezed MAX_FREEZE_TIMEOUT cycles after last looking at
if(IS_CRYING_ANGEL == 1)
{
Freeze = is_looking_at()
if(Freeze == 1)
{
FreezeTimeout = 0
}
else
{
FreezeTimeout = FreezeTimeout+1
}
if(FreezeTimeout < MAX_FREEZE_TIMEOUT)
{
return
}
}
Target = chooseTarget()
if(Target == noentity())
{
return
}
TargetPos = Target:pos() + Target:boxCenter()
# Do not kill the player, hide behind his back
if(IS_BEHIND_THE_BACK == 1)
{
Eye = Target:eye()
EyePos = TargetPos - vec(Eye:x(), Eye:y(), 0):normalized() * BEHIND_THE_BACK_DIST
LookToPos = TargetPos
TargetPos = EyePos
}
else
{
LookToPos = TargetPos
}
move(TargetPos, LookToPos)
if(IS_TELEPORT)
{
teleport(TargetPos)
}
if(IS_DETONATE)
{
Dist = (TargetPos - Base:pos()):length()
detonate(Dist)
}
}
if(first() | duped())
{
setup()
}
else
{
loop()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment