-- LUA Script - precede every function and global member with lowercase name of script + '_main' -- DESCRIPTION: smallg's portal gun script -- DESCRIPTION: link two entitys to this object to use as the portals -- DESCRIPTION: [GUNNAME$=""] (the gun's name to use as the portal gun) -- DESCRIPTION: [ISPROJECTILE!=0] (tick if gun is a projectile weapon (not instant bullet) -- DESCRIPTION: [PLAYERHEIGHT=30] (adjusts the starting point of the raycast) -- DESCRIPTION: [SHOWHELDGUN!=0] (for easy set up tick this to show the currently held gun) -- DESCRIPTION: [PORTALRANGE=200] (how close to portal you can get before it teleports you) -- DESCRIPTION: [PORTALDELAY=1] (how long till the portal activates) -- DESCRIPTION: [ANIMATONSPEED=1] (how quickly the teleportanimation plays) -- DESCRIPTION: sound slot 1 on portals will play when porting U = U or require "scriptbank\\utillib" g_portalgun = {} local pg local rad = math.rad local currentportal = 1 local fired_recently = false local bothportalsactive = 0 local ported_recently = 2 local timer = {} local teleportspr = nil local teleportimg = {} local current_img = 1 local isporting = false local rotating = {} local desiredFacing = {} function portalgun_properties(e, gunname, isprojectile, playerheight, showheldgun, portalrange, portaldelay, animationspeed) g_portalgun[e] = {} pg = g_portalgun[e] pg.gunname = gunname pg.isprojectile = isprojectile pg.playerheight = playerheight pg.showheldgun = showheldgun pg.portalrange = portalrange pg.portaldelay = portaldelay pg.animationspeed = animationspeed end function portalgun_init(e) local function file_exists(name) local f=io.open(name,"r") if f~=nil then io.close(f) return true else return false end end g_portalgun[e] = {} pg = g_portalgun[e] pg.gunname = "" pg.isprojectile = 0 pg.playerheight = 30 pg.showheldgun = 0 pg.portalrange = 200 pg.portaldelay = 1 pg.animationspeed = 1 timer[e] = {} timer[e][1] = nil local imagename = "" for a = 0, 99 do imagename = "teleport_0" if a < 10 then imagename = imagename.."0" elseif a > 99 then imagename = "teleport_" end imagename = imagename..a..".png" local path = "scriptbank\\smallg_max\\images\\teleport\\" local file = path..imagename if file_exists(file) == true then teleportimg[a] = LoadImage(file) if teleportspr == nil then teleportspr = CreateSprite(LoadImage(path.."teleport_099.png")) SetSpriteSize(teleportspr,100,100) SetSpritePosition(teleportspr,2000,2000) end end end end function portalgun_main(e) local function RaycastTerrain(x1,y1,z1,x2,y2,z2) local r = RayTerrain(x1,y1,z1,x2,y2,z2) -- returns 1 if the ray cast hits the terrain geometry local x3,y3,z3 = 0,0,0 if r ~= 0 then x3 = GetRayCollisionX() -- returns the X position of the terrain hit position y3 = GetRayCollisionY() -- returns the Y position of the terrain hit position z3 = GetRayCollisionZ() -- returns the Z position of the terrain hit position end return x3,y3,z3 end local function RaycastStatic(x1,y1,z1,x2,y2,z2,IgnoreObj) local x3,y3,z3 = 0,0,0 local xn,yn,zn = 0,0,0 local r = IntersectStatic(x1,y1,z1,x2,y2,z2,IgnoreObj) -- returns 1 if the ray cast hits ANY entity or lightmapped geometry if r ~= nil then --x=IntersectStatic(x1,y1,z1,x2,y2,z2,IgnoreObj) -- returns 1 if the ray cast hits non-animating entity or lightmapped geometry x3 = GetIntersectCollisionX() -- returns the X position of the entity hit position y3 = GetIntersectCollisionY() -- returns the Y position of the entity hit position z3 = GetIntersectCollisionZ() -- returns the Z position of the entity hit position xn = GetIntersectCollisionNX() -- returns the X normal of the entity hit surface yn = GetIntersectCollisionNY() -- returns the Y normal of the entity hit surface zn = GetIntersectCollisionNZ() -- returns the Z normal of the entity hit surface else r = 0 end return x3,y3,z3,xn,yn,zn,r end local function RaycastALL(x1,y1,z1,x2,y2,z2,IgnoreObj) local x3,y3,z3 = 0,0,0 local xn,yn,zn = 0,0,0 local r = IntersectAll(x1,y1,z1,x2,y2,z2,IgnoreObj) -- returns 1 if the ray cast hits ANY entity or lightmapped geometry if r ~= nil then --x=IntersectStatic(x1,y1,z1,x2,y2,z2,IgnoreObj) -- returns 1 if the ray cast hits non-animating entity or lightmapped geometry x3 = GetIntersectCollisionX() -- returns the X position of the entity hit position y3 = GetIntersectCollisionY() -- returns the Y position of the entity hit position z3 = GetIntersectCollisionZ() -- returns the Z position of the entity hit position xn = GetIntersectCollisionNX() -- returns the X normal of the entity hit surface yn = GetIntersectCollisionNY() -- returns the Y normal of the entity hit surface zn = GetIntersectCollisionNZ() -- returns the Z normal of the entity hit surface else r = 0 end return x3,y3,z3,xn,yn,zn,r end if pg.portal1 == nil then for a = 0, 9 do local connectede = GetEntityRelationshipID(e, a) if connectede > 0 then if pg.portal1 == nil then pg.portal1 = connectede rotating[1] = 0 desiredFacing[1] = {} elseif pg.portal2 == nil then pg.portal2 = connectede rotating[2] = 0 desiredFacing[2] = {} else break end end end if pg.portal1 == nil or pg.portal2 == nil then return end end if g_PlayerGunName ~= "" then --if no gun set then we will just get the current gun when one is held if pg.gunname == "" then pg.gunname = g_PlayerGunName end --check if the player fired a gun if g_PlayerGunFired == 1 then if fired_recently == false then fired_recently = true end else if fired_recently == true then --check if it was the portal gun if pg.gunname == g_PlayerGunName then --if it's an instant bullet gun we need to ray cast for position if pg.isprojectile == 0 then local x1,y1,z1 = g_PlayerPosX, g_PlayerPosY+pg.playerheight, g_PlayerPosZ local xa1,ya1,za1 = g_PlayerAngX, g_PlayerAngY, g_PlayerAngZ xa1 = rad(xa1) ya1 = rad(ya1) za1 = rad(za1) local ox,oy,oz = U.Rotate3D( 0, 0, 3000, xa1, ya1, za1 ) x2,y2,z3 = x1+ox,y1+oy,z1+oz local rx,ry,rz = RaycastTerrain(x1,y1,z1,x2,y2,z2) --check if we hit the terrain (doesn't appear to be working in Max beta 3) if rx ~= 0 and ry ~= 0 and rz ~= 0 then --[[ -- don't want to stick portals to the terrain as they have no normals --PromptDuration(rx.." "..ry.." "..rz, 5000) local p1 = pg.portal1 local p2 = pg.portal2 if currentportal == 1 or p2 == 0 then if p1 > 0 then CollisionOff(p1) SetPosition(p1,rx,ry,rz) desiredFacing[1][1] = rx+rxn * 100 desiredFacing[1][2] = ry+ryn * 100 desiredFacing[1][3] = rz+rzn * 100 rotating[1] = 2 currentportal = 2 end elseif p2 > 0 then CollisionOff(p2) SetPosition(p2,rx,ry,rz) desiredFacing[2][1] = rx+rxn * 100 desiredFacing[2][2] = ry+ryn * 100 desiredFacing[2][3] = rz+rzn * 100 rotating[2] = 2 currentportal = 1 end --]] else local rxn,ryn,rzn,r = 0,0,0,0 rx,ry,rz,rxn,ryn,rzn,r = RaycastALL(x1,y1,z1,x2,y2,z2,0) if r ~= 0 then --hit a dynamic object (should be if ~= 0) local p1 = pg.portal1 local p2 = pg.portal2 if currentportal == 1 or p2 == 0 then if p1 > 0 then CollisionOff(p1) SetPosition(p1,rx,ry,rz) desiredFacing[1][1] = rx+rxn * 100 desiredFacing[1][2] = ry+ryn * 100 desiredFacing[1][3] = rz+rzn * 100 rotating[1] = 2 currentportal = 2 end elseif p2 > 0 then CollisionOff(p2) SetPosition(p2,rx,ry,rz) desiredFacing[2][1] = rx+rxn * 100 desiredFacing[2][2] = ry+ryn * 100 desiredFacing[2][3] = rz+rzn * 100 rotating[2] = 2 currentportal = 1 end --PromptDuration(r,1000) else --should check static objects but not working yet rxn,ryn,rzn,r = 0,0,0,0 rx,ry,rz,rxn,ryn,rzn,r = RaycastStatic(x1,y1,z1,x2,y2,z2,0) if r == 0 then --hit a static object local p1 = pg.portal1 local p2 = pg.portal2 if currentportal == 1 or p2 == 0 then if p1 > 0 then CollisionOff(p1) GravityOff(p1) SetPosition(p1,rx,ry,rz) ResetPosition(p1,rx,ry,rz) desiredFacing[1][1] = rx+rxn * 100 desiredFacing[1][2] = ry+ryn * 100 desiredFacing[1][3] = rz+rzn * 100 --RotateToPlayer(p1) --CollisionOn(p1) rotating[1] = 2 currentportal = 2 end elseif p2 > 0 then CollisionOff(p2) GravityOff(p2) SetPosition(p2,rx,ry,rz) ResetPosition(p2,rx,ry,rz) desiredFacing[2][1] = rx+rxn * 100 desiredFacing[2][2] = ry+ryn * 100 desiredFacing[2][3] = rz+rzn * 100 rotating[2] = 2 currentportal = 1 end end end end --is a projectile so we need to check the collision info later else end end if currentportal == 2 and bothportalsactive == 0 then bothportalsactive = 1 elseif currentportal == 1 and bothportalsactive == 1 then bothportalsactive = 2 end end fired_recently = false end for a = 1,2 do if rotating[a] > 0 then local p if a == 1 then p = pg.portal1 else p = pg.portal2 end --CollisionOff(p) --GravityOff(p) RotateXYToPoint(p,desiredFacing[a][1],desiredFacing[a][2],desiredFacing[a][3],0) MoveForward(p,60) rotating[a] = rotating[a] - 1 --PromptLocal(p, "moving portal "..a.." , rotating = "..rotating[a]) end end if bothportalsactive == 2 then local p1 = pg.portal1 local p2 = pg.portal2 local distp1 = GetPlayerDistance(p1) local distp2 = GetPlayerDistance(p2) --PromptLocal(p1,distp1) --PromptLocal(p2,distp2) --Text(50,90,3,ported_recently) if ported_recently ~= 1 then if distp1 < pg.portalrange then if timer[e][1] == nil then timer[e][1] = g_Time + (pg.portaldelay*1000) end if g_Time > timer[e][1] then SetFreezePosition(g_Entity[p2]['x'],g_Entity[p2]['y'],g_Entity[p2]['z']) SetFreezeAngle(g_Entity[p2]['anglex'],g_Entity[p2]['angley'],g_Entity[p2]['anglez']) TransportToFreezePosition() UnFreezePlayer() PlaySound(p2,1) ported_recently = 2 timer[e][1] = nil isporting = false else if isporting == false then current_img = 1 isporting = true SetFreezeAngle(g_PlayerAngX,g_PlayerAngY,g_PlayerAngZ) SetFreezePosition(g_PlayerPosX,g_PlayerPosY,g_PlayerPosZ) TransportToFreezePosition() PlaySound(p1,0) FreezePlayer() else if teleportimg[current_img] ~= nil then SetSpriteImage(teleportspr,teleportimg[current_img]) end PasteSpritePosition(teleportspr,0,0) current_img = current_img + pg.animationspeed if current_img > 100 then current_img = current_img - 100 end end end elseif distp2 > pg.portalrange and ported_recently == 2 then ported_recently = 0 end end if ported_recently ~= 2 then if distp2 < pg.portalrange then if timer[e][1] == nil then timer[e][1] = g_Time + (pg.portaldelay*1000) end if g_Time > timer[e][1] then SetFreezePosition(g_Entity[p1]['x'],g_Entity[p1]['y'],g_Entity[p1]['z']) SetFreezeAngle(g_Entity[p1]['anglex'],g_Entity[p1]['angley'],g_Entity[p1]['anglez']) TransportToFreezePosition() UnFreezePlayer() PlaySound(p1,1) ported_recently = 1 timer[e][1] = nil isporting = false else if isporting == false then SetFreezeAngle(g_PlayerAngX,g_PlayerAngY,g_PlayerAngZ) SetFreezePosition(g_PlayerPosX,g_PlayerPosY,g_PlayerPosZ) TransportToFreezePosition() FreezePlayer() current_img = 1 PlaySound(p2,0) isporting = true else if teleportimg[current_img] ~= nil then SetSpriteImage(teleportspr,teleportimg[current_img]) end PasteSpritePosition(teleportspr,0,0) current_img = current_img + pg.animationspeed if current_img > 100 then current_img = current_img - 100 end end end elseif distp1 > pg.portalrange and ported_recently == 1 then ported_recently = 0 end end end --show the currently held gun if option is ticked if pg.showheldgun == 1 then Prompt(g_PlayerGunName) end --TextCenterOnX(50,90,3,g_PlayerPosY) end end function RotateXYToPoint(e,x,y,z,ymod) local function GetXYRotationToPoint(e,x,y,z) local x1,y1,z1 = x,y,z local x2,y2,z2 = GetEntityPosAng(e) local tay = math.atan2( x2 - x1, z2 - z1 ) * ( 180.0 / math.pi ) local nx = math.abs(x2 - x1) local nz = math.abs(z2 - z1) local tax = 0 if nx > nz then else tax = math.atan2( x2 - x1, y2 - y1 ) * ( 180.0 / math.pi ) end local dx, dz = x2 - x1, z2 - z1 local dist = math.sqrt(dx*dx+dz*dz) tax = math.atan2(dist , y2 - y1 ) * ( 180.0 / math.pi ) --no idea why we take away 90 degrees here but it seems to be needed to face the z direction rather than up tax = tax - 90 return tax,tay,0 end if ymod == nil then ymod = 0 end local tax,tay, taz = GetXYRotationToPoint(e,x,y,z) --CollisionOff(e) SetRotation(e,tax,tay+ymod,taz) --ResetRotation(e,tax,tay+ymod,taz) end