Water (again)
<div class="IPBDescription">a water trigger that mappers can use</div>I made water before, but then it was just a sphere placed with rifle, and swimming easily made oyu stuck in roof. But thanks to max latest q&a I now know how to add entities into the spark editor!
So I have changed the shape of the water into that of a trigger (basiclly a brush), which mappers can place and scale and such.
Please note that there currently is alot of odd glitches with how the trigger entity works, atleast when it comes to players, if anyone know how to solve them please tell me.
Examples being the enter/exit hooks running several times without the opposite running inbetween, or how you can exit a trigger and yet not have the exit hook run.
I have not made any graphics for the water, so it will be invisible ingame. While in editor its a white box.
My test map was a simple box with a ready room spawn, a map_extents, an ambient light, and 2 water, one shallow and one tall (with manually created faces to tell me where each started/ended).
The swimming is implemented inside player.lua, and I will release the whole file instead of only my changes. I have however added " - by feha" at the end of the first comment in each such section. So if you want to read my code just search for that.
<!--coloro:#FF0000--><span style="color:#FF0000"><!--/coloro--><!--sizeo:3--><span style="font-size:12pt;line-height:100%"><!--/sizeo--><b>The player.lua is a huge file, so you might want to just grab the slider and drag past it!</b><!--sizec--></span><!--/sizec--><!--colorc--></span><!--/colorc-->
Water.lua
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// ======= Copyright stuff, I give ppl permission to use this hoever they want, altough preferably with putting me in the credits =======
//
// lua\Water.lua
//
// By Feha
//
//It is supposed to sort of mimic how water brushes in source works
//
// ========= For more information, visit http://www.unknownworlds.com =====================
class 'Water' (Trigger)
Water.kMapName = "water"
function Water:OnInit()
Trigger.OnInit(self)
self.physicsBody:SetCollisionEnabled(true)
end
function Water:OnTriggerEntered(enterEnt, triggerEnt)
--Only affect players so far
if enterEnt:isa("Player") then
--Offset so you cant swim with only feet in water
entOrigin = enterEnt:GetOrigin() + Vector(0,0,3)
if (self:GetIsPointInside(entOrigin)) then
enterEnt.inWater = self:GetName()
end
end
end
function Water:OnTriggerExited(exitEnt, triggerEnt)
if exitEnt:isa("Player") then
entOrigin = exitEnt:GetOrigin() + Vector(0,0,3)
if (not self:GetIsPointInside(entOrigin)) then
--Names is used as when you enter a new water trigger from an old one, you still havent left the first one
--Without names you could enter one and then exit, thus falling
--Still has glitches tho, as the enter hook often run before exit, making you fall at the transition
--I will probably revert this to be a boolean, as I rather see that first glitch when more complex water systems is used
if (exitEnt.inWater ~= nil and self:GetName() ~= nil and exitEnt.inWater == self:GetName()) then
exitEnt.inWater = ""
end
end
end
end
function Water:OnCreate()
Trigger.OnCreate(self)
self:SetPropagate(Actor.Propagate_Never)
self:SetIsVisible(false)
end
Shared.LinkClassToMap("Water", Water.kMapName, {})<!--c2--></div><!--ec2-->
Player.lua
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// ======= Copyright © 2003-2010, Unknown Worlds Entertainment, Inc. All rights reserved. =======
//
// lua\Player.lua
//
// Created by: Charlie Cleveland (charlie@unknownworlds.com)
//
// Player coordinates - z is forward, x is to the left, y is up.
// The origin of the player is at their feet.
//
// ========= For more information, visit us at http://www.unknownworlds.com =====================
Script.Load("lua/Globals.lua")
Script.Load("lua/TechData.lua")
Script.Load("lua/Utility.lua")
Script.Load("lua/LiveScriptActor.lua")
Script.Load("lua/PhysicsGroups.lua")
class 'Player' (LiveScriptActor)
if (Server) then
Script.Load("lua/Player_Server.lua")
Script.Load("lua/Bot_Player.lua")
else
Script.Load("lua/Player_Client.lua")
Script.Load("lua/Chat.lua")
end
Player.kMapName = "player"
Player.kModelName = PrecacheAsset("models/marine/male/male.model")
Player.kSpecialModelName = PrecacheAsset("models/marine/male/male_special.model")
Player.kClientConnectSoundName = PrecacheAsset("sound/ns2.fev/common/connect")
Player.kNotEnoughResourcesSound = PrecacheAsset("sound/ns2.fev/marine/voiceovers/commander/more")
Player.kInvalidSound = PrecacheAsset("sound/ns2.fev/common/invalid")
Player.kTooltipSound = PrecacheAsset("sound/ns2.fev/common/tooltip")
Player.kChatSound = PrecacheAsset("sound/ns2.fev/common/chat")
// Animations
Player.kAnimRun = "run"
Player.kAnimTaunt = "taunt"
Player.kAnimStartJump = "jumpin"
Player.kAnimEndJump = "jumpout"
Player.kAnimJump = "jump"
Player.kAnimReload = "reload"
Player.kRunIdleSpeed = 1
Player.kLoginBreakingDistance = 150
Player.kUseRange = 1.6
Player.kUseHolsterTime = .5
Player.kDefaultBuildTime = .2
Player.kGravity = -24
Player.kMass = 90.7 // ~200 pounds (incl. armor, weapons)
Player.kWalkBackwardSpeedScalar = 0.4
Player.kJumpHeight = 1
// The physics shapes used for player collision have a "skin" that makes them appear to float, this makes the shape
// smaller so they don't appear to float anymore
Player.kSkinCompensation = 0.9
Player.kXZExtents = 0.35
Player.kYExtents = .95
Player.kViewOffsetHeight = Player.kYExtents * 2 - .28 // Eyes a bit below the top of the head. NS1 marine was 64" tall.
Player.kFov = 90
Player.kToolTipInterval = 18
// Percentage change in height when full crouched
Player.kCrouchShrinkAmount = .5
// Slow down players when crouching
Player.kCrouchSpeedScalar = .5
// How long does it take to crouch or uncrouch
Player.kCrouchAnimationTime = .25
Player.kMinVelocityForGravity = .5
Player.kThinkInterval = .2
Player.kMinimumPlayerVelocity = .05 // Minimum player velocity for network performance and ease of debugging
// Player speeds
Player.kWalkMaxSpeed = 5 // Four miles an hour = 6,437 meters/hour = 1.8 meters/second (increase for FPS tastes)
Player.kStartRunMaxSpeed = Player.kWalkMaxSpeed
Player.kRunMaxSpeed = 6.25 // 10 miles an hour = 16,093 meters/hour = 4.4 meters/second (increase for FPS tastes)
Player.kMaxWalkableNormal = math.cos( math.rad(45) )
Player.kAcceleration = 50
Player.kRunAcceleration = 300
Player.kLadderAcceleration = 50
//Swim speeds - by feha
Player.kSwimForward = 1250 --Ridiculusly high because I added * input.time :P
Player.kSwimStrafe = 1250
Player.kSwimUp = 25
// Out of breath
Player.kTimeToLoseBreath = 10
Player.kTimeToGainBreath = 20
Player.kTauntMovementScalar = .05 // Players can only move a little while taunting
Player.kDamageIndicatorDrawTime = 1
Player.kMaxHotkeyGroups = 5
Player.kUnstickDistance = .1
Player.kUnstickOffsets = {
Vector(0, Player.kUnstickDistance, 0),
Vector(Player.kUnstickDistance, 0, 0),
Vector(-Player.kUnstickDistance, 0, 0),
Vector(0, 0, Player.kUnstickDistance),
Vector(0, 0, -Player.kUnstickDistance)
}
// When changing these, make sure to update Player:CopyPlayerDataFrom. Any data which
// needs to survive between player class changes needs to go in here.
// Compensated variables are things that you want reverted when processing commands
// so basically things that are important for defining whether or not something can be shot
// for the player this is anything that can affect the hit boxes, like the animation that's playing,
// the current animation time, pose parameters, etc (not for the player firing but for the
// player being shot).
local networkVars =
{
// Compensated means backed up and restored between calls to OnProcessMove, but only for other players, not yourself.
viewYaw = "compensated interpolated angle",
viewPitch = "compensated interpolated angle",
viewRoll = "compensated interpolated angle",
cameraDistance = "float",
desiredCameraDistance = "float",
thirdPerson = "boolean",
smoothCamera = "boolean",
// Controlling client index. -1 for not being controlled by a live player (ragdoll, fake player)
clientIndex = "integer",
// In degrees
fov = "integer (0 to 255)",
velocity = "compensated interpolated vector",
gravityEnabled = "boolean",
// 0 means no active weapon, 1 means first child weapon, etc.
activeWeaponIndex = "integer (0 to 10)",
activeWeaponHolstered = "boolean",
viewModelId = "entityid",
plasma = string.format("integer (0 to %d)", kMaxResources),
teamCarbon = string.format("integer (0 to %d)", kMaxResources),
gameStarted = "boolean",
countingDown = "boolean",
frozen = "boolean",
timeOfDeath = "float",
timeOfLastUse = "float",
timeOfLastWeaponSwitch = "float",
crouching = "compensated boolean",
timeOfCrouchChange = "compensated interpolated float",
timeLegalCrouchTime = "compensated interpolated float",
flareStartTime = "float",
flareStopTime = "float",
flareScalar = "float",
desiredPitch = "float",
desiredRoll = "float",
showScoreboard = "boolean",
sayingsMenu = "integer (0 to 6)",
timeLastMenu = "float",
// True if target under reticle can be damaged
reticleTarget = "boolean",
// Time we last did damage to a target
timeTargetHit = "float",
// Set to true when jump key has been released after jump processed
// Used to require the key to pressed multiple times
jumpHandled = "boolean",
timeOfLastJump = "float",
onGround = "boolean",
onGroundNeedsUpdate = "boolean",
onLadder = "boolean",
//I want this to end up with prediction, prolyl isnt atm tho - by feha
inWater = string.format("string (%d)", kMaxEntityStringLength),
// Player-specific mode. When set to kPlayerMode.Default, player moves and acts normally, otherwise
// he doesn't take player input. Change mode and set modeTime to the game time that the mode
// ends. ProcessEndMode() will be called when the mode ends. Return true from that to process
// that mode change, otherwise it will go back to kPlayerMode.Default. Used for things like taunting,
// building structures and other player actions that take time while the player is stationary.
mode = "enum kPlayerMode",
// Time when mode will end. Set to -1 to have it never end.
modeTime = "float",
primaryAttackLastFrame = "boolean",
secondaryAttackLastFrame = "boolean",
// Indicates how active the player has been
outOfBreath = "integer (0 to 255)",
baseYaw = "float",
basePitch = "float",
baseRoll = "float",
// The next point in the world to go to in order to reach an order target location
nextOrderWaypoint = "vector",
// The final point in the world to go to in order to reach an order target location
finalWaypoint = "vector",
// Whether this entity has a next order waypoint
nextOrderWaypointActive = "boolean",
// Move, Build, etc.
waypointType = "enum kTechId",
fallReadyForPlay = "integer (0 to 3)",
}
function Player:OnCreate()
LiveScriptActor.OnCreate(self)
self:SetLagCompensated(true)
self:SetUpdates(true)
// Create the controller for doing collision detection.
// Just use default values for the capsule size for now. Player will update to correct
// values when they are known.
self:CreateController(PhysicsGroup.PlayerControllersGroup, 1, 0.5)
self.viewYaw = 0
self.viewPitch = 0
self.viewRoll = 0
self.maxExtents = Vector( LookupTechData(self:GetTechId(), kTechDataMaxExtents, Vector(Player.kXZExtents, Player.kYExtents, Player.kXZExtents)) )
self.viewOffset = Vector()
self.desiredCameraDistance = 0
self.thirdPerson = false
self.smoothCamera = false
self.clientIndex = -1
self.client = nil
self.cameraDistance = 0
self.velocity = Vector(0, 0, 0)
self.gravityEnabled = true
self.activeWeaponIndex = 0
self.activeWeaponHolstered = false
self.overlayAnimationName = ""
self.showScoreboard = false
if Server then
self.sendTechTreeBase = false
end
if Client then
self.showSayings = false
end
self.sayingsMenu = 0
self.timeLastMenu = 0
self.timeLastSayingsAction = 0
self.reticleTarget = false
self.timeTargetHit = 0
self.score = 0
self.kills = 0
self.deaths = 0
self.displayedTooltips = {}
self.sighted = false
self.jumpHandled = false
self.leftFoot = true
self.mode = kPlayerMode.Default
self.modeTime = -1
self.primaryAttackLastFrame = false
self.secondaryAttackLastFrame = false
self.outOfBreath = 0
self.baseYaw = 0
self.basePitch = 0
self.baseRoll = 0
self.requestsScores = false
self.viewModelId = Entity.invalidId
self.usingStructure = nil
self.timeOfLastUse = 0
self.timeOfLastWeaponSwitch = nil
self.respawnQueueEntryTime = nil
self.timeOfDeath = nil
self.crouching = false
self.timeOfCrouchChange = 0
self.timeLegalCrouchTime = 0
self.onGroundNeedsUpdate = true
self.onGround = false
self.onLadder = false
self.timeLastOnGround = 0
self.fallReadyForPlay = 0
self.flareStartTime = 0
self.flareStopTime = 0
self.flareScalar = 1
self.plasma = 0
// Make the player kinematic so that bullets and other things collide with it.
self:SetPhysicsGroup(PhysicsGroup.PlayerGroup)
self.nextOrderWaypoint = nil
self.finalWaypoint = nil
self.nextOrderWaypointActive = false
self.waypointType = kTechId.None
end
function Player:OnInit()
LiveScriptActor.OnInit(self)
if Server then
self:InitWeapons()
end
// Set true on creation
if Server then
self:SetName(kDefaultPlayerName)
end
self:SetScoreboardChanged(true)
self:SetViewOffsetHeight(self:GetMaxViewOffsetHeight())
self:SetFov(self:GetStartFov())
self:SetFov(self:GetStartFov())
self:UpdateControllerFromEntity()
self:TriggerEffects("idle")
if Server then
self:SetNextThink(Player.kThinkInterval)
end
// Initialize hotkey groups. This is in player because
// it needs to be preserved across player replacements.
// Table of table of ids, in order of hotkey groups
self:InitializeHotkeyGroups()
self:LoadHeightmap()
end
function Player:InitializeHotkeyGroups()
self.hotkeyGroups = {}
for i = 1, Player.kMaxHotkeyGroups do
table.insert(self.hotkeyGroups, {})
end
end
function Player:OnEntityChange(oldEntityId, newEntityId)
if Server then
// Loop through hotgroups and update accordingly
for i = 1, Player.kMaxHotkeyGroups do
for index, entityId in ipairs(self.hotkeyGroups[i]) do
if(entityId == oldEntityId) then
if(newEntityId ~= nil) then
self.hotkeyGroups[i][index] = newEntityId
else
table.remove(self.hotkeyGroups[i], index)
end
if self.SendHotkeyGroup ~= nil then
self:SendHotkeyGroup(i)
end
end
end
end
end
end
function Player:GetStatusDescription()
return string.format("%s - %s", self:GetName(), self:GetClassName()), nil
end
function Player:GetHealthDescription()
return "Health", self:GetHealth() / self:GetMaxHealth()
end
// Special unique client-identifier
function Player:GetClientIndex()
return self.clientIndex
end
/**
* Sets the view angles for the player. Note that setting the yaw of the
* view will also adjust the player's yaw. Pass true for second parameter
* to indicate that this is from player input.
*/
function Player:SetViewAngles(viewAngles, playerInput)
self.viewYaw = viewAngles.yaw + self.baseYaw
self.viewPitch = viewAngles.pitch + self.basePitch
self.viewRoll = viewAngles.roll + self.baseRoll
local angles = Angles(self:GetAngles())
angles.yaw = self.viewYaw
self:SetAngles(angles)
end
function Player:SetOffsetAngles(offsetAngles)
self:SetBaseViewAngles(offsetAngles)
self:SetViewAngles(Angles(0, 0, 0))
self:SetAngles(offsetAngles)
if Server then
Server.SendNetworkMessage(self, "ResetMouse", {}, true)
else
Client.SetPitch(0)
Client.SetYaw(0)
end
end
/**
* Gets the view angles for the player.
*/
function Player:GetViewAngles()
return Angles(self.viewPitch, self.viewYaw, self.viewRoll)
end
function Player:GetViewAnglesCoords()
local currentCoords = self:GetViewAngles():GetCoords()
VectorCopy(self:GetOrigin(), currentCoords.origin)
currentCoords.origin = currentCoords.origin + self:GetViewOffset()
return currentCoords
end
function Player:SetBaseViewAngles(viewAngles)
self.baseYaw = viewAngles.yaw
self.basePitch = viewAngles.pitch
self.baseRoll = viewAngles.roll
end
/**
* Whenever view angles are needed this function must be called
* to compute them.
*/
function Player:ConvertToViewAngles(forPitch, forYaw, forRoll)
return Angles(forPitch + self.basePitch, forYaw + self.baseYaw, forRoll + self.baseRoll)
end
function Player:OverrideInput(input)
// Invert mouse if specified in options
local invertMouse = Client.GetOptionBoolean ( kInvertedMouseOptionsKey, false )
if invertMouse then
input.pitch = -input.pitch
end
local maxPitch = Math.Radians(89.9)
input.pitch = Math.Clamp(input.pitch, -maxPitch, maxPitch)
if self.timeClosedMenu and (Shared.GetTime() < self.timeClosedMenu + .25) then
// Don't allow weapon firing
local removePrimaryAttackMask = bit.bxor(0xFFFFFFFF, Move.PrimaryAttack)
input.commands = bit.band(input.commands, removePrimaryAttackMask)
end
self:OverrideSayingsMenu(input)
end
function Player:OverrideSayingsMenu(input)
if(self:GetHasSayings() and (bit.band(input.commands, Move.ToggleSayings1) ~= 0 or bit.band(input.commands, Move.ToggleSayings2) ~= 0)) then
// If enough time has passed
if(self.timeLastSayingsAction == nil or (Shared.GetTime() > self.timeLastSayingsAction + .2)) then
local newMenu = ConditionalValue(bit.band(input.commands, Move.ToggleSayings1) ~= 0, 1, 2)
// If not visible, bring up menu
if(not self.showSayings) then
self.showSayings = true
self.showSayingsMenu = newMenu
// else if same menu and visible, hide it
elseif(newMenu == self.showSayingsMenu) then
self.showSayings = false
self.showSayingsMenu = nil
// If different, change menu without showing or hiding
elseif(newMenu ~= self.showSayingsMenu) then
self.showSayingsMenu = newMenu
end
end
// Sayings toggles are handled client side.
local removeToggleSayingsMask = bit.bxor(0xFFFFFFFF, Move.ToggleSayings1)
input.commands = bit.band(input.commands, removeToggleSayingsMask)
removeToggleSayingsMask = bit.bxor(0xFFFFFFFF, Move.ToggleSayings2)
input.commands = bit.band(input.commands, removeToggleSayingsMask)
// Record time
self.timeLastSayingsAction = Shared.GetTime()
end
// Intercept any execute sayings commands.
if self.showSayings then
local weaponSwitchCommands = { Move.Weapon1, Move.Weapon2, Move.Weapon3, Move.Weapon4, Move.Weapon5 }
for i, weaponSwitchCommand in ipairs(weaponSwitchCommands) do
if bit.band(input.commands, weaponSwitchCommand) ~= 0 then
// Tell the server to execute this saying.
local message = BuildExecuteSayingMessage(i, self.showSayingsMenu)
Client.SendNetworkMessage("ExecuteSaying", message, true)
local removeWeaponMask = bit.bxor(0xFFFFFFFF, weaponSwitchCommand)
input.commands = bit.band(input.commands, removeWeaponMask)
self.showSayings = false
end
end
end
end
// Returns current FOV
function Player:GetFov()
return self.fov
end
function Player:SetFov(fov)
self.fov = fov
end
function Player:SetGravityEnabled(state)
self.gravityEnabled = state
end
// Initial FOV when spawning as class. Can change through console
// commands, weapons, etc. but this is the base.
function Player:GetStartFov()
return Player.kFov
end
function Player:SetDesiredCameraDistance(distance)
self.desiredCameraDistance = math.max(distance, 0)
self.thirdPerson = ((self.desiredCameraDistance > 0) or (self.cameraDistance > 0))
end
function Player:UpdateCamera(timePassed)
if(self.cameraDistance ~= self.desiredCameraDistance) then
local diff = (self.desiredCameraDistance - self.cameraDistance)
local change = ConditionalValue(GetSign(diff) > 0, 10 * timePassed, -16 * timePassed)
local newCameraDistance = self.cameraDistance + change
if(math.abs(diff) < math.abs(change)) then
newCameraDistance = self.desiredCameraDistance
end
self:SetCameraDistance(newCameraDistance)
end
end
function Player:SetCameraDistance(distance)
self.cameraDistance = math.max(distance, 0)
self.thirdPerson = ((self.desiredCameraDistance > 0) or (self.cameraDistance > 0))
end
function Player:GetIsThirdPerson()
return self.thirdPerson
end
// Set to 0 to get out of third person
function Player:SetIsThirdPerson(distance)
self:SetDesiredCameraDistance(distance)
end
function Player:GetIsFirstPerson()
return (Client and (Client.GetLocalPlayer() == self) and not self:GetIsThirdPerson())
end
function Player:GetCameraDistance()
return self.cameraDistance
end
// Also specifies listener position
function Player:GetViewOffset()
return self.viewOffset
end
// Stores the player's current view offset. Calculated from GetMaxViewOffset() and crouch state.
function Player:SetViewOffsetHeight(newViewOffsetHeight)
VectorCopy(Vector(0, newViewOffsetHeight, 0), self.viewOffset)
end
function Player:GetEyePos()
return self:GetOrigin() + self.viewOffset
end
function Player:GetMaxViewOffsetHeight()
return Player.kViewOffsetHeight
end
function Player:GetCanViewModelIdle()
return self:GetIsAlive() and self:GetCanNewActivityStart() and (self.mode == kPlayerMode.Default)
end
function Player:LoadHeightmap()
// Load height map
self.heightmap = HeightMap()
local heightmapFilename = string.format("maps/overviews/%s.hmp", Shared.GetMapName())
if(not self.heightmap:Load(heightmapFilename)) then
Shared.Message("Couldn't load height map " .. heightmapFilename)
self.heightmap = nil
end
end
function Player:GetHeightmap()
return self.heightmap
end
// worldX => -map y
// worldZ => +map x
function Player:GetMapXY(worldX, worldZ)
local success = false
local mapX = 0
local mapY = 0
if self.heightmap then
mapX = self.heightmap:GetMapX(worldZ)
mapY = self.heightmap:GetMapY(worldX)
else
--Print("Player:GetMapXY(): heightmap is nil")
return false, 0, 0
end
if mapX >= 0 and mapX <= 1 and mapY >= 0 and mapY <= 1 then
success = true
end
return success, mapX, mapY
end
// Plays view model animation, given a string or a table of weighted entries.
// Returns length of animation or 0 if animation wasn't found.
function Player:SetViewAnimation(animName, noForce, blend, speed)
local length = 0.0
if not speed then
speed = 1
end
if (animName ~= nil and animName ~= "") then
local viewModel = self:GetViewModelEntity()
if (viewModel ~= nil) then
local force = ConditionalValue(noForce, false, true)
local success = false
if blend then
success = viewModel:SetAnimationWithBlending(animName, self:GetBlendTime(), force, speed)
length = viewModel:GetAnimationLength(animName) / speed
else
success = viewModel:SetAnimation(animName, force, speed)
length = viewModel:GetAnimationLength(animName) / speed
end
if success then
if Client then
self:UpdateRenderModel()
end
viewModel:UpdateBoneCoords()
end
if not success and force then
Print("%s:SetViewAnimation(%s) failed.", self:GetClassName(), tostring(animSpecifier))
end
else
Print("Player:SetViewAnimation(%s) - couldn't find view model", animName)
end
end
return length
end
function Player:GetViewAnimationLength(animName)
local length = 0
local viewModel = self:GetViewModelEntity()
if (viewModel ~= nil) then
if animName and animName ~= "" then
length = viewModel:GetAnimationLength(animName)
else
length = viewModel:GetAnimationLength(nil)
end
end
return length
end
function Player:SetViewOverlayAnimation(overlayAnim)
local viewModel = self:GetViewModelEntity()
if (viewModel ~= nil) then
viewModel:SetOverlayAnimation(overlayAnim)
end
end
function Player:GetVelocity()
return self.velocity
end
function Player:SetVelocity(velocity)
VectorCopy(velocity, self.velocity)
// Snap to 0 when close to zero for network performance and our own sanity
if (math.abs(self.velocity:GetLength()) < Player.kMinimumPlayerVelocity) then
self.velocity:Scale(0)
end
end
function Player:GetController()
return self.controller
end
function Player:PrimaryAttack()
local weapon = self:GetActiveWeapon()
if weapon and self:GetCanNewActivityStart() then
weapon:OnPrimaryAttack(self)
end
end
function Player:SecondaryAttack()
local weapon = self:GetActiveWeapon()
if weapon and self:GetCanNewActivityStart() then
weapon:OnSecondaryAttack(self)
end
end
function Player:PrimaryAttackEnd()
local weapon = self:GetActiveWeapon()
if weapon then
weapon:OnPrimaryAttackEnd(self)
end
end
function Player:SecondaryAttackEnd()
local weapon = self:GetActiveWeapon()
if weapon then
weapon:OnSecondaryAttackEnd(self)
end
end
function Player:SelectNextWeapon()
self:SelectWeaponWithFinder(self.FindChildEntity)
end
function Player:SelectPrevWeapon()
self:SelectWeaponWithFinder(self.FindChildEntityReverse)
end
function Player:SelectWeaponWithFinder(finderFunction)
local entity = finderFunction(self, self:GetActiveWeapon())
if(entity == nil) then
entity = finderFunction(self, nil)
end
while(entity ~= nil and not entity:isa("Weapon")) do
entity = finderFunction(self, entity)
end
if(entity ~= nil and self:GetCanNewActivityStart()) then
self:SetActiveWeapon(entity:GetMapName())
end
end
function Player:GetActiveWeapon()
local activeWeapon = nil
if(self.activeWeaponIndex ~= 0) then
local weapons = self:GetHUDOrderedWeaponList()
if self.activeWeaponIndex <= table.count(weapons) then
activeWeapon = weapons[self.activeWeaponIndex]
end
end
return activeWeapon
end
function Player:GetActiveWeaponName()
local activeWeaponName = ""
local activeWeapon = self:GetActiveWeapon()
if activeWeapon ~= nil then
activeWeaponName = activeWeapon:GetClassName()
end
return activeWeaponName
end
function Player:Reload()
local weapon = self:GetActiveWeapon()
if(weapon ~= nil and self:GetCanNewActivityStart()) then
weapon:OnReload(self)
end
end
/**
* Check to see if there's a LiveScriptActor we can use. Checks any attachpoints returned from
* GetAttachPointOrigin() and if that fails, does a regular traceray. Returns true if we processed the action.
*/
function Player:Use()
local success = false
local startPoint = self:GetViewOffset() + self:GetOrigin()
local viewCoords = self:GetViewAngles():GetCoords()
local elapsedTime = 0
if self.timeOfLastUse ~= 0 then
elapsedTime = math.min(Shared.GetTime() - self.timeOfLastUse, Player.kDefaultBuildTime)
end
// Get entities in radius
local ents = GetEntitiesIsaInRadius("LiveScriptActor", self:GetTeamNumber(), self:GetOrigin(), Player.kUseRange)
for index, entity in ipairs(ents) do
// Look for attach point
local attachPointName = entity:GetUseAttachPoint()
if attachPointName ~= "" and entity:GetCanBeUsed(self) then
local attachPoint = entity:GetAttachPointOrigin(attachPointName)
local toAttachPoint = attachPoint - startPoint
local legalUse = toAttachPoint:GetLength() < Player.kUseRange and viewCoords.zAxis:DotProduct(GetNormalizedVector(toAttachPoint)) > .8
if(legalUse and entity:OnUse(self, elapsedTime, true, attachPoint)) then
success = true
break
end
end
end
// If failed, do a regular trace with entities that don't have use attach points
if not success then
local endPoint = startPoint + viewCoords.zAxis * Player.kUseRange
local trace = Shared.TraceRay(startPoint, endPoint, PhysicsMask.AllButPCs, EntityFilterOne(self))
So I have changed the shape of the water into that of a trigger (basiclly a brush), which mappers can place and scale and such.
Please note that there currently is alot of odd glitches with how the trigger entity works, atleast when it comes to players, if anyone know how to solve them please tell me.
Examples being the enter/exit hooks running several times without the opposite running inbetween, or how you can exit a trigger and yet not have the exit hook run.
I have not made any graphics for the water, so it will be invisible ingame. While in editor its a white box.
My test map was a simple box with a ready room spawn, a map_extents, an ambient light, and 2 water, one shallow and one tall (with manually created faces to tell me where each started/ended).
The swimming is implemented inside player.lua, and I will release the whole file instead of only my changes. I have however added " - by feha" at the end of the first comment in each such section. So if you want to read my code just search for that.
<!--coloro:#FF0000--><span style="color:#FF0000"><!--/coloro--><!--sizeo:3--><span style="font-size:12pt;line-height:100%"><!--/sizeo--><b>The player.lua is a huge file, so you might want to just grab the slider and drag past it!</b><!--sizec--></span><!--/sizec--><!--colorc--></span><!--/colorc-->
Water.lua
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// ======= Copyright stuff, I give ppl permission to use this hoever they want, altough preferably with putting me in the credits =======
//
// lua\Water.lua
//
// By Feha
//
//It is supposed to sort of mimic how water brushes in source works
//
// ========= For more information, visit http://www.unknownworlds.com =====================
class 'Water' (Trigger)
Water.kMapName = "water"
function Water:OnInit()
Trigger.OnInit(self)
self.physicsBody:SetCollisionEnabled(true)
end
function Water:OnTriggerEntered(enterEnt, triggerEnt)
--Only affect players so far
if enterEnt:isa("Player") then
--Offset so you cant swim with only feet in water
entOrigin = enterEnt:GetOrigin() + Vector(0,0,3)
if (self:GetIsPointInside(entOrigin)) then
enterEnt.inWater = self:GetName()
end
end
end
function Water:OnTriggerExited(exitEnt, triggerEnt)
if exitEnt:isa("Player") then
entOrigin = exitEnt:GetOrigin() + Vector(0,0,3)
if (not self:GetIsPointInside(entOrigin)) then
--Names is used as when you enter a new water trigger from an old one, you still havent left the first one
--Without names you could enter one and then exit, thus falling
--Still has glitches tho, as the enter hook often run before exit, making you fall at the transition
--I will probably revert this to be a boolean, as I rather see that first glitch when more complex water systems is used
if (exitEnt.inWater ~= nil and self:GetName() ~= nil and exitEnt.inWater == self:GetName()) then
exitEnt.inWater = ""
end
end
end
end
function Water:OnCreate()
Trigger.OnCreate(self)
self:SetPropagate(Actor.Propagate_Never)
self:SetIsVisible(false)
end
Shared.LinkClassToMap("Water", Water.kMapName, {})<!--c2--></div><!--ec2-->
Player.lua
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// ======= Copyright © 2003-2010, Unknown Worlds Entertainment, Inc. All rights reserved. =======
//
// lua\Player.lua
//
// Created by: Charlie Cleveland (charlie@unknownworlds.com)
//
// Player coordinates - z is forward, x is to the left, y is up.
// The origin of the player is at their feet.
//
// ========= For more information, visit us at http://www.unknownworlds.com =====================
Script.Load("lua/Globals.lua")
Script.Load("lua/TechData.lua")
Script.Load("lua/Utility.lua")
Script.Load("lua/LiveScriptActor.lua")
Script.Load("lua/PhysicsGroups.lua")
class 'Player' (LiveScriptActor)
if (Server) then
Script.Load("lua/Player_Server.lua")
Script.Load("lua/Bot_Player.lua")
else
Script.Load("lua/Player_Client.lua")
Script.Load("lua/Chat.lua")
end
Player.kMapName = "player"
Player.kModelName = PrecacheAsset("models/marine/male/male.model")
Player.kSpecialModelName = PrecacheAsset("models/marine/male/male_special.model")
Player.kClientConnectSoundName = PrecacheAsset("sound/ns2.fev/common/connect")
Player.kNotEnoughResourcesSound = PrecacheAsset("sound/ns2.fev/marine/voiceovers/commander/more")
Player.kInvalidSound = PrecacheAsset("sound/ns2.fev/common/invalid")
Player.kTooltipSound = PrecacheAsset("sound/ns2.fev/common/tooltip")
Player.kChatSound = PrecacheAsset("sound/ns2.fev/common/chat")
// Animations
Player.kAnimRun = "run"
Player.kAnimTaunt = "taunt"
Player.kAnimStartJump = "jumpin"
Player.kAnimEndJump = "jumpout"
Player.kAnimJump = "jump"
Player.kAnimReload = "reload"
Player.kRunIdleSpeed = 1
Player.kLoginBreakingDistance = 150
Player.kUseRange = 1.6
Player.kUseHolsterTime = .5
Player.kDefaultBuildTime = .2
Player.kGravity = -24
Player.kMass = 90.7 // ~200 pounds (incl. armor, weapons)
Player.kWalkBackwardSpeedScalar = 0.4
Player.kJumpHeight = 1
// The physics shapes used for player collision have a "skin" that makes them appear to float, this makes the shape
// smaller so they don't appear to float anymore
Player.kSkinCompensation = 0.9
Player.kXZExtents = 0.35
Player.kYExtents = .95
Player.kViewOffsetHeight = Player.kYExtents * 2 - .28 // Eyes a bit below the top of the head. NS1 marine was 64" tall.
Player.kFov = 90
Player.kToolTipInterval = 18
// Percentage change in height when full crouched
Player.kCrouchShrinkAmount = .5
// Slow down players when crouching
Player.kCrouchSpeedScalar = .5
// How long does it take to crouch or uncrouch
Player.kCrouchAnimationTime = .25
Player.kMinVelocityForGravity = .5
Player.kThinkInterval = .2
Player.kMinimumPlayerVelocity = .05 // Minimum player velocity for network performance and ease of debugging
// Player speeds
Player.kWalkMaxSpeed = 5 // Four miles an hour = 6,437 meters/hour = 1.8 meters/second (increase for FPS tastes)
Player.kStartRunMaxSpeed = Player.kWalkMaxSpeed
Player.kRunMaxSpeed = 6.25 // 10 miles an hour = 16,093 meters/hour = 4.4 meters/second (increase for FPS tastes)
Player.kMaxWalkableNormal = math.cos( math.rad(45) )
Player.kAcceleration = 50
Player.kRunAcceleration = 300
Player.kLadderAcceleration = 50
//Swim speeds - by feha
Player.kSwimForward = 1250 --Ridiculusly high because I added * input.time :P
Player.kSwimStrafe = 1250
Player.kSwimUp = 25
// Out of breath
Player.kTimeToLoseBreath = 10
Player.kTimeToGainBreath = 20
Player.kTauntMovementScalar = .05 // Players can only move a little while taunting
Player.kDamageIndicatorDrawTime = 1
Player.kMaxHotkeyGroups = 5
Player.kUnstickDistance = .1
Player.kUnstickOffsets = {
Vector(0, Player.kUnstickDistance, 0),
Vector(Player.kUnstickDistance, 0, 0),
Vector(-Player.kUnstickDistance, 0, 0),
Vector(0, 0, Player.kUnstickDistance),
Vector(0, 0, -Player.kUnstickDistance)
}
// When changing these, make sure to update Player:CopyPlayerDataFrom. Any data which
// needs to survive between player class changes needs to go in here.
// Compensated variables are things that you want reverted when processing commands
// so basically things that are important for defining whether or not something can be shot
// for the player this is anything that can affect the hit boxes, like the animation that's playing,
// the current animation time, pose parameters, etc (not for the player firing but for the
// player being shot).
local networkVars =
{
// Compensated means backed up and restored between calls to OnProcessMove, but only for other players, not yourself.
viewYaw = "compensated interpolated angle",
viewPitch = "compensated interpolated angle",
viewRoll = "compensated interpolated angle",
cameraDistance = "float",
desiredCameraDistance = "float",
thirdPerson = "boolean",
smoothCamera = "boolean",
// Controlling client index. -1 for not being controlled by a live player (ragdoll, fake player)
clientIndex = "integer",
// In degrees
fov = "integer (0 to 255)",
velocity = "compensated interpolated vector",
gravityEnabled = "boolean",
// 0 means no active weapon, 1 means first child weapon, etc.
activeWeaponIndex = "integer (0 to 10)",
activeWeaponHolstered = "boolean",
viewModelId = "entityid",
plasma = string.format("integer (0 to %d)", kMaxResources),
teamCarbon = string.format("integer (0 to %d)", kMaxResources),
gameStarted = "boolean",
countingDown = "boolean",
frozen = "boolean",
timeOfDeath = "float",
timeOfLastUse = "float",
timeOfLastWeaponSwitch = "float",
crouching = "compensated boolean",
timeOfCrouchChange = "compensated interpolated float",
timeLegalCrouchTime = "compensated interpolated float",
flareStartTime = "float",
flareStopTime = "float",
flareScalar = "float",
desiredPitch = "float",
desiredRoll = "float",
showScoreboard = "boolean",
sayingsMenu = "integer (0 to 6)",
timeLastMenu = "float",
// True if target under reticle can be damaged
reticleTarget = "boolean",
// Time we last did damage to a target
timeTargetHit = "float",
// Set to true when jump key has been released after jump processed
// Used to require the key to pressed multiple times
jumpHandled = "boolean",
timeOfLastJump = "float",
onGround = "boolean",
onGroundNeedsUpdate = "boolean",
onLadder = "boolean",
//I want this to end up with prediction, prolyl isnt atm tho - by feha
inWater = string.format("string (%d)", kMaxEntityStringLength),
// Player-specific mode. When set to kPlayerMode.Default, player moves and acts normally, otherwise
// he doesn't take player input. Change mode and set modeTime to the game time that the mode
// ends. ProcessEndMode() will be called when the mode ends. Return true from that to process
// that mode change, otherwise it will go back to kPlayerMode.Default. Used for things like taunting,
// building structures and other player actions that take time while the player is stationary.
mode = "enum kPlayerMode",
// Time when mode will end. Set to -1 to have it never end.
modeTime = "float",
primaryAttackLastFrame = "boolean",
secondaryAttackLastFrame = "boolean",
// Indicates how active the player has been
outOfBreath = "integer (0 to 255)",
baseYaw = "float",
basePitch = "float",
baseRoll = "float",
// The next point in the world to go to in order to reach an order target location
nextOrderWaypoint = "vector",
// The final point in the world to go to in order to reach an order target location
finalWaypoint = "vector",
// Whether this entity has a next order waypoint
nextOrderWaypointActive = "boolean",
// Move, Build, etc.
waypointType = "enum kTechId",
fallReadyForPlay = "integer (0 to 3)",
}
function Player:OnCreate()
LiveScriptActor.OnCreate(self)
self:SetLagCompensated(true)
self:SetUpdates(true)
// Create the controller for doing collision detection.
// Just use default values for the capsule size for now. Player will update to correct
// values when they are known.
self:CreateController(PhysicsGroup.PlayerControllersGroup, 1, 0.5)
self.viewYaw = 0
self.viewPitch = 0
self.viewRoll = 0
self.maxExtents = Vector( LookupTechData(self:GetTechId(), kTechDataMaxExtents, Vector(Player.kXZExtents, Player.kYExtents, Player.kXZExtents)) )
self.viewOffset = Vector()
self.desiredCameraDistance = 0
self.thirdPerson = false
self.smoothCamera = false
self.clientIndex = -1
self.client = nil
self.cameraDistance = 0
self.velocity = Vector(0, 0, 0)
self.gravityEnabled = true
self.activeWeaponIndex = 0
self.activeWeaponHolstered = false
self.overlayAnimationName = ""
self.showScoreboard = false
if Server then
self.sendTechTreeBase = false
end
if Client then
self.showSayings = false
end
self.sayingsMenu = 0
self.timeLastMenu = 0
self.timeLastSayingsAction = 0
self.reticleTarget = false
self.timeTargetHit = 0
self.score = 0
self.kills = 0
self.deaths = 0
self.displayedTooltips = {}
self.sighted = false
self.jumpHandled = false
self.leftFoot = true
self.mode = kPlayerMode.Default
self.modeTime = -1
self.primaryAttackLastFrame = false
self.secondaryAttackLastFrame = false
self.outOfBreath = 0
self.baseYaw = 0
self.basePitch = 0
self.baseRoll = 0
self.requestsScores = false
self.viewModelId = Entity.invalidId
self.usingStructure = nil
self.timeOfLastUse = 0
self.timeOfLastWeaponSwitch = nil
self.respawnQueueEntryTime = nil
self.timeOfDeath = nil
self.crouching = false
self.timeOfCrouchChange = 0
self.timeLegalCrouchTime = 0
self.onGroundNeedsUpdate = true
self.onGround = false
self.onLadder = false
self.timeLastOnGround = 0
self.fallReadyForPlay = 0
self.flareStartTime = 0
self.flareStopTime = 0
self.flareScalar = 1
self.plasma = 0
// Make the player kinematic so that bullets and other things collide with it.
self:SetPhysicsGroup(PhysicsGroup.PlayerGroup)
self.nextOrderWaypoint = nil
self.finalWaypoint = nil
self.nextOrderWaypointActive = false
self.waypointType = kTechId.None
end
function Player:OnInit()
LiveScriptActor.OnInit(self)
if Server then
self:InitWeapons()
end
// Set true on creation
if Server then
self:SetName(kDefaultPlayerName)
end
self:SetScoreboardChanged(true)
self:SetViewOffsetHeight(self:GetMaxViewOffsetHeight())
self:SetFov(self:GetStartFov())
self:SetFov(self:GetStartFov())
self:UpdateControllerFromEntity()
self:TriggerEffects("idle")
if Server then
self:SetNextThink(Player.kThinkInterval)
end
// Initialize hotkey groups. This is in player because
// it needs to be preserved across player replacements.
// Table of table of ids, in order of hotkey groups
self:InitializeHotkeyGroups()
self:LoadHeightmap()
end
function Player:InitializeHotkeyGroups()
self.hotkeyGroups = {}
for i = 1, Player.kMaxHotkeyGroups do
table.insert(self.hotkeyGroups, {})
end
end
function Player:OnEntityChange(oldEntityId, newEntityId)
if Server then
// Loop through hotgroups and update accordingly
for i = 1, Player.kMaxHotkeyGroups do
for index, entityId in ipairs(self.hotkeyGroups[i]) do
if(entityId == oldEntityId) then
if(newEntityId ~= nil) then
self.hotkeyGroups[i][index] = newEntityId
else
table.remove(self.hotkeyGroups[i], index)
end
if self.SendHotkeyGroup ~= nil then
self:SendHotkeyGroup(i)
end
end
end
end
end
end
function Player:GetStatusDescription()
return string.format("%s - %s", self:GetName(), self:GetClassName()), nil
end
function Player:GetHealthDescription()
return "Health", self:GetHealth() / self:GetMaxHealth()
end
// Special unique client-identifier
function Player:GetClientIndex()
return self.clientIndex
end
/**
* Sets the view angles for the player. Note that setting the yaw of the
* view will also adjust the player's yaw. Pass true for second parameter
* to indicate that this is from player input.
*/
function Player:SetViewAngles(viewAngles, playerInput)
self.viewYaw = viewAngles.yaw + self.baseYaw
self.viewPitch = viewAngles.pitch + self.basePitch
self.viewRoll = viewAngles.roll + self.baseRoll
local angles = Angles(self:GetAngles())
angles.yaw = self.viewYaw
self:SetAngles(angles)
end
function Player:SetOffsetAngles(offsetAngles)
self:SetBaseViewAngles(offsetAngles)
self:SetViewAngles(Angles(0, 0, 0))
self:SetAngles(offsetAngles)
if Server then
Server.SendNetworkMessage(self, "ResetMouse", {}, true)
else
Client.SetPitch(0)
Client.SetYaw(0)
end
end
/**
* Gets the view angles for the player.
*/
function Player:GetViewAngles()
return Angles(self.viewPitch, self.viewYaw, self.viewRoll)
end
function Player:GetViewAnglesCoords()
local currentCoords = self:GetViewAngles():GetCoords()
VectorCopy(self:GetOrigin(), currentCoords.origin)
currentCoords.origin = currentCoords.origin + self:GetViewOffset()
return currentCoords
end
function Player:SetBaseViewAngles(viewAngles)
self.baseYaw = viewAngles.yaw
self.basePitch = viewAngles.pitch
self.baseRoll = viewAngles.roll
end
/**
* Whenever view angles are needed this function must be called
* to compute them.
*/
function Player:ConvertToViewAngles(forPitch, forYaw, forRoll)
return Angles(forPitch + self.basePitch, forYaw + self.baseYaw, forRoll + self.baseRoll)
end
function Player:OverrideInput(input)
// Invert mouse if specified in options
local invertMouse = Client.GetOptionBoolean ( kInvertedMouseOptionsKey, false )
if invertMouse then
input.pitch = -input.pitch
end
local maxPitch = Math.Radians(89.9)
input.pitch = Math.Clamp(input.pitch, -maxPitch, maxPitch)
if self.timeClosedMenu and (Shared.GetTime() < self.timeClosedMenu + .25) then
// Don't allow weapon firing
local removePrimaryAttackMask = bit.bxor(0xFFFFFFFF, Move.PrimaryAttack)
input.commands = bit.band(input.commands, removePrimaryAttackMask)
end
self:OverrideSayingsMenu(input)
end
function Player:OverrideSayingsMenu(input)
if(self:GetHasSayings() and (bit.band(input.commands, Move.ToggleSayings1) ~= 0 or bit.band(input.commands, Move.ToggleSayings2) ~= 0)) then
// If enough time has passed
if(self.timeLastSayingsAction == nil or (Shared.GetTime() > self.timeLastSayingsAction + .2)) then
local newMenu = ConditionalValue(bit.band(input.commands, Move.ToggleSayings1) ~= 0, 1, 2)
// If not visible, bring up menu
if(not self.showSayings) then
self.showSayings = true
self.showSayingsMenu = newMenu
// else if same menu and visible, hide it
elseif(newMenu == self.showSayingsMenu) then
self.showSayings = false
self.showSayingsMenu = nil
// If different, change menu without showing or hiding
elseif(newMenu ~= self.showSayingsMenu) then
self.showSayingsMenu = newMenu
end
end
// Sayings toggles are handled client side.
local removeToggleSayingsMask = bit.bxor(0xFFFFFFFF, Move.ToggleSayings1)
input.commands = bit.band(input.commands, removeToggleSayingsMask)
removeToggleSayingsMask = bit.bxor(0xFFFFFFFF, Move.ToggleSayings2)
input.commands = bit.band(input.commands, removeToggleSayingsMask)
// Record time
self.timeLastSayingsAction = Shared.GetTime()
end
// Intercept any execute sayings commands.
if self.showSayings then
local weaponSwitchCommands = { Move.Weapon1, Move.Weapon2, Move.Weapon3, Move.Weapon4, Move.Weapon5 }
for i, weaponSwitchCommand in ipairs(weaponSwitchCommands) do
if bit.band(input.commands, weaponSwitchCommand) ~= 0 then
// Tell the server to execute this saying.
local message = BuildExecuteSayingMessage(i, self.showSayingsMenu)
Client.SendNetworkMessage("ExecuteSaying", message, true)
local removeWeaponMask = bit.bxor(0xFFFFFFFF, weaponSwitchCommand)
input.commands = bit.band(input.commands, removeWeaponMask)
self.showSayings = false
end
end
end
end
// Returns current FOV
function Player:GetFov()
return self.fov
end
function Player:SetFov(fov)
self.fov = fov
end
function Player:SetGravityEnabled(state)
self.gravityEnabled = state
end
// Initial FOV when spawning as class. Can change through console
// commands, weapons, etc. but this is the base.
function Player:GetStartFov()
return Player.kFov
end
function Player:SetDesiredCameraDistance(distance)
self.desiredCameraDistance = math.max(distance, 0)
self.thirdPerson = ((self.desiredCameraDistance > 0) or (self.cameraDistance > 0))
end
function Player:UpdateCamera(timePassed)
if(self.cameraDistance ~= self.desiredCameraDistance) then
local diff = (self.desiredCameraDistance - self.cameraDistance)
local change = ConditionalValue(GetSign(diff) > 0, 10 * timePassed, -16 * timePassed)
local newCameraDistance = self.cameraDistance + change
if(math.abs(diff) < math.abs(change)) then
newCameraDistance = self.desiredCameraDistance
end
self:SetCameraDistance(newCameraDistance)
end
end
function Player:SetCameraDistance(distance)
self.cameraDistance = math.max(distance, 0)
self.thirdPerson = ((self.desiredCameraDistance > 0) or (self.cameraDistance > 0))
end
function Player:GetIsThirdPerson()
return self.thirdPerson
end
// Set to 0 to get out of third person
function Player:SetIsThirdPerson(distance)
self:SetDesiredCameraDistance(distance)
end
function Player:GetIsFirstPerson()
return (Client and (Client.GetLocalPlayer() == self) and not self:GetIsThirdPerson())
end
function Player:GetCameraDistance()
return self.cameraDistance
end
// Also specifies listener position
function Player:GetViewOffset()
return self.viewOffset
end
// Stores the player's current view offset. Calculated from GetMaxViewOffset() and crouch state.
function Player:SetViewOffsetHeight(newViewOffsetHeight)
VectorCopy(Vector(0, newViewOffsetHeight, 0), self.viewOffset)
end
function Player:GetEyePos()
return self:GetOrigin() + self.viewOffset
end
function Player:GetMaxViewOffsetHeight()
return Player.kViewOffsetHeight
end
function Player:GetCanViewModelIdle()
return self:GetIsAlive() and self:GetCanNewActivityStart() and (self.mode == kPlayerMode.Default)
end
function Player:LoadHeightmap()
// Load height map
self.heightmap = HeightMap()
local heightmapFilename = string.format("maps/overviews/%s.hmp", Shared.GetMapName())
if(not self.heightmap:Load(heightmapFilename)) then
Shared.Message("Couldn't load height map " .. heightmapFilename)
self.heightmap = nil
end
end
function Player:GetHeightmap()
return self.heightmap
end
// worldX => -map y
// worldZ => +map x
function Player:GetMapXY(worldX, worldZ)
local success = false
local mapX = 0
local mapY = 0
if self.heightmap then
mapX = self.heightmap:GetMapX(worldZ)
mapY = self.heightmap:GetMapY(worldX)
else
--Print("Player:GetMapXY(): heightmap is nil")
return false, 0, 0
end
if mapX >= 0 and mapX <= 1 and mapY >= 0 and mapY <= 1 then
success = true
end
return success, mapX, mapY
end
// Plays view model animation, given a string or a table of weighted entries.
// Returns length of animation or 0 if animation wasn't found.
function Player:SetViewAnimation(animName, noForce, blend, speed)
local length = 0.0
if not speed then
speed = 1
end
if (animName ~= nil and animName ~= "") then
local viewModel = self:GetViewModelEntity()
if (viewModel ~= nil) then
local force = ConditionalValue(noForce, false, true)
local success = false
if blend then
success = viewModel:SetAnimationWithBlending(animName, self:GetBlendTime(), force, speed)
length = viewModel:GetAnimationLength(animName) / speed
else
success = viewModel:SetAnimation(animName, force, speed)
length = viewModel:GetAnimationLength(animName) / speed
end
if success then
if Client then
self:UpdateRenderModel()
end
viewModel:UpdateBoneCoords()
end
if not success and force then
Print("%s:SetViewAnimation(%s) failed.", self:GetClassName(), tostring(animSpecifier))
end
else
Print("Player:SetViewAnimation(%s) - couldn't find view model", animName)
end
end
return length
end
function Player:GetViewAnimationLength(animName)
local length = 0
local viewModel = self:GetViewModelEntity()
if (viewModel ~= nil) then
if animName and animName ~= "" then
length = viewModel:GetAnimationLength(animName)
else
length = viewModel:GetAnimationLength(nil)
end
end
return length
end
function Player:SetViewOverlayAnimation(overlayAnim)
local viewModel = self:GetViewModelEntity()
if (viewModel ~= nil) then
viewModel:SetOverlayAnimation(overlayAnim)
end
end
function Player:GetVelocity()
return self.velocity
end
function Player:SetVelocity(velocity)
VectorCopy(velocity, self.velocity)
// Snap to 0 when close to zero for network performance and our own sanity
if (math.abs(self.velocity:GetLength()) < Player.kMinimumPlayerVelocity) then
self.velocity:Scale(0)
end
end
function Player:GetController()
return self.controller
end
function Player:PrimaryAttack()
local weapon = self:GetActiveWeapon()
if weapon and self:GetCanNewActivityStart() then
weapon:OnPrimaryAttack(self)
end
end
function Player:SecondaryAttack()
local weapon = self:GetActiveWeapon()
if weapon and self:GetCanNewActivityStart() then
weapon:OnSecondaryAttack(self)
end
end
function Player:PrimaryAttackEnd()
local weapon = self:GetActiveWeapon()
if weapon then
weapon:OnPrimaryAttackEnd(self)
end
end
function Player:SecondaryAttackEnd()
local weapon = self:GetActiveWeapon()
if weapon then
weapon:OnSecondaryAttackEnd(self)
end
end
function Player:SelectNextWeapon()
self:SelectWeaponWithFinder(self.FindChildEntity)
end
function Player:SelectPrevWeapon()
self:SelectWeaponWithFinder(self.FindChildEntityReverse)
end
function Player:SelectWeaponWithFinder(finderFunction)
local entity = finderFunction(self, self:GetActiveWeapon())
if(entity == nil) then
entity = finderFunction(self, nil)
end
while(entity ~= nil and not entity:isa("Weapon")) do
entity = finderFunction(self, entity)
end
if(entity ~= nil and self:GetCanNewActivityStart()) then
self:SetActiveWeapon(entity:GetMapName())
end
end
function Player:GetActiveWeapon()
local activeWeapon = nil
if(self.activeWeaponIndex ~= 0) then
local weapons = self:GetHUDOrderedWeaponList()
if self.activeWeaponIndex <= table.count(weapons) then
activeWeapon = weapons[self.activeWeaponIndex]
end
end
return activeWeapon
end
function Player:GetActiveWeaponName()
local activeWeaponName = ""
local activeWeapon = self:GetActiveWeapon()
if activeWeapon ~= nil then
activeWeaponName = activeWeapon:GetClassName()
end
return activeWeaponName
end
function Player:Reload()
local weapon = self:GetActiveWeapon()
if(weapon ~= nil and self:GetCanNewActivityStart()) then
weapon:OnReload(self)
end
end
/**
* Check to see if there's a LiveScriptActor we can use. Checks any attachpoints returned from
* GetAttachPointOrigin() and if that fails, does a regular traceray. Returns true if we processed the action.
*/
function Player:Use()
local success = false
local startPoint = self:GetViewOffset() + self:GetOrigin()
local viewCoords = self:GetViewAngles():GetCoords()
local elapsedTime = 0
if self.timeOfLastUse ~= 0 then
elapsedTime = math.min(Shared.GetTime() - self.timeOfLastUse, Player.kDefaultBuildTime)
end
// Get entities in radius
local ents = GetEntitiesIsaInRadius("LiveScriptActor", self:GetTeamNumber(), self:GetOrigin(), Player.kUseRange)
for index, entity in ipairs(ents) do
// Look for attach point
local attachPointName = entity:GetUseAttachPoint()
if attachPointName ~= "" and entity:GetCanBeUsed(self) then
local attachPoint = entity:GetAttachPointOrigin(attachPointName)
local toAttachPoint = attachPoint - startPoint
local legalUse = toAttachPoint:GetLength() < Player.kUseRange and viewCoords.zAxis:DotProduct(GetNormalizedVector(toAttachPoint)) > .8
if(legalUse and entity:OnUse(self, elapsedTime, true, attachPoint)) then
success = true
break
end
end
end
// If failed, do a regular trace with entities that don't have use attach points
if not success then
local endPoint = startPoint + viewCoords.zAxis * Player.kUseRange
local trace = Shared.TraceRay(startPoint, endPoint, PhysicsMask.AllButPCs, EntityFilterOne(self))
Comments
I kinda stopped developing water after posting this as it seemed like noone was interested at all, making it seem like quite much work for an 'unwanted' feature :P.
I am not sure if I should stick with triggers and wait for them to get fixed, or do that but change to find the players bounding box and check for intersection between it and the trigger.
The alternative is to actually make my own trigger entity, but that feels like unnecesary work :S...
EDIT:
Maybe I could try to not check if collided entity is a player, but instead create some sort of bounding box entity which I stick to player and then let the water check if collided entity is that.
I'm not big to mess around with this stuff as I don't know what I'm doing. Any chance of a preview video showing what's going on your side with this effect?
You guys in here keep on amazing me with all your LUA scripting =]
If you wanna know how it looks, go into the map eedito and create a team_join. The physical representation in editor looks the same, as they both use the same base entity (trigger, not sure if you can spawn it in editor tho).
Ingame you cant see the water, I have not made any graphics whatsoever. If you walk into water the gravity is lowered, and totally removed when you try to walk (so you can go upwards xD), and if you hold space you start float upwards. The swim model (not graphical model, control model, just like lerks have a flight model xD) is designed to be a lot like how its in hl2, as the description might make you understand XD.
Looking in thirdperson is hilarious, as the marine is walking midair XD
EDIT:
Also, hos water in that map looks awesome! Particles light that can simulate dynamic water XD.
I think that I will end up letting mappers set the water to be invisible, so that it can be used together with awesome particle fx XD.
Also, I figure that as 1 file is completely custom, another is simply appending a few lines at end, and a third is 1 line aslong as it is below a certain point, the only big work I need to do is in player.lua
And because I added a comment to find all my code changes, it wont be really hard to put them back at the right places anyway XD.
Still, you are right there is better ways to do it, and I will look into it later. Now I must sleep tho ;P.