-- LUA Script - precede every function and global member with lowercase name of script + '_main' -- -- pickuppable script -- ================== -- -- Assign script to all items to be manipulated by the player -- -------------------------- -- user editable values -- -------------------------- local maxWeight = 800 -- default maximum weight of pickuppable -- or stack of pickuppables local maxSize = 400 -- default maximum size of pickuppable local minSpeed = 0.05 -- speed of movement at max weight local carryOfst = 0 -- x offset of carrying position \ local carryHght = 27 -- y offset of carrying position | relative to player local carryDist = 60 -- z offset of carrying position / local levelAng = 20 -- number of degrees off horizontal still considered -- as 'level' for stacking purposes local handSize = 15 -- size of hand icon (screen %) local throwEnabled = true -- Picked up items can be thrown local pushEnabled = true -- Also Pull -------------------------- local U = require "scriptbank\\utillib" local Q = require "scriptbank\\quatlib" local P = require "scriptbank\\physlib" local modf = math.modf local deg = math.deg local rad = math.rad local max = math.max local min = math.min local abs = math.abs local sin = math.sin local cos = math.cos local atan = math.atan2 local sqrt = math.sqrt local log = math.log local rnd = math.random -- return a copy of a table/list (move to utils when tested) local function deepcopy( orig ) local orig_type = type( orig ) local copy if orig_type == 'table' then copy = {} for orig_key, orig_value in next, orig, nil do copy[ deepcopy( orig_key ) ] = deepcopy( orig_value ) end setmetatable( copy, deepcopy( getmetatable( orig ) ) ) else -- number, string, boolean, etc copy = orig end return copy end -- ============================== -- -- global variables for save/load -- -- ============================== -- g_pickuppables_list = g_pickuppables_list or {} -- fill g_pickuppables_list with saved version for situation -- where saving at a checkpoint if g_pickuppables_list == {} and g_pickuppables_snapshot ~= nil then g_pickuppables_list = deepcopy( g_pickuppables_snapshot ) end -- ============================== -- -- control lists local dimensions = {} local names = {} local pEnt = {} local carryingEnt = nil -- 'pickuppable' actually in hand (i.e. bottom of any 'Stack' local droppedEnt = nil -- 'pickuppable' last to be in hand (i.e. last to be dropped) local promptsOn = true -- whether to show Prompts to player or not local thrownObj = nil -- details of thrown objects for collision purposes -- ================================= -- -- start of global functions section -- -- ================================= -- function PU_EnableThrowing() throwEnabled = true g_pickuppables_list.throwEnabled = throwEnabled end function PU_CanThrow() return throwEnabled end function PU_DisableThrowing() throwEnabled = false g_pickuppables_list.throwEnabled = throwEnabled end function PU_EnablePushPull() pushEnabled = true g_pickuppables_list.pushEnabled = pushEnabled end function PU_CanPush() return pushEnabled end function PU_DisablePushPull() pushEnabled = false g_pickuppables_list.pushEnabled = pushEnabled end -- global functions to read/set max weight we can lift function PU_GetMaxCarryWeight() return maxWeight end function PU_SetMaxCarryWeight( value ) maxWeight = value g_pickuppables_list.maxWeight = maxWeight end -- global functions to read/set max size of item we can lift function PU_GetMaxCarrySize() return maxSize end function PU_SetMaxCarrySize( value ) maxSize = value g_pickuppables_list.maxSize = maxSize end function PU_DisablePrompts() promptsOn = false end -- stops entity specified from being 'pickuppable' function PU_RemoveEntity( e ) local remList = g_pickuppables_list.removed if remList == nil then g_pickuppables_list.removed = {} remList = g_pickuppables_list.removed end remList[ e ] = true dimensions[ e ] = nil pEnt[ e ] = nil end -- returns nil or entity id of item being carried function PU_GetEntityCarried() return carryingEnt end -- returns nil or entity id of last dropped item function PU_LastDropped() return droppedEnt end -- returns empty list or list of all carried items function PU_GetCarriedList() local tempList = {} if carryingEnt == nil then return tempList end templist[ #templist + 1 ] = carryingEnt local function getStackItems( p ) if p.stackList == nil then return end for k, v in pairs( p.stackItems ) do templist[ #templist + 1 ] = k getStackItems( pEnt[ k ] ) end end getStackItems( pEnt[ carryingEnt ] ) return tempList end -- returns a list of carried items by name function PU_CarriedNames() local tempList = {} for _, v in pairs( PU_GetCarriedList() ) do templist[ #templist + 1 ] = names[ v ] end return templist end -- creates a copy of the current state for save/load purposes function PU_SnapshotGlobalList() g_pickuppables_snapshot = deepcopy( g_pickuppables_list ) end -- End of global functions -- -- ======================= -- local mouse1Clicked = false local mouse2Clicked = false local mouseTimer = math.huge -- sprite 'pointers' local Sprites = {} local handShowing = 'none' local function HideHand( hand ) SetSpritePosition( Sprites[ hand ], 200, 200) handShowing = 'none' end local function ShowHand( hand ) SetSpritePosition( Sprites[ hand ], 50, 50) handShowing = hand end local function stackWeight( p ) local totWeight = 0 if p.stackList ~= nil then for k, p in pairs( p.stackList ) do totWeight = totWeight + ( dimensions[ p.obj ].m or 0 ) + stackWeight( pEnt[ k ] ) end end return totWeight end local function ObjectPickuppable( obj ) if obj == nil or obj == 0 then return false end for _,v in pairs( pEnt ) do if v.obj == obj then return true end end return false end local function getDims( obj ) dimensions[ obj ] = P.GetObjectDimensions( obj ) end ------------------------------------------- -- Init routine - called once per entity -- -- at spawn time -- ------------------------------------------- function pickuppable_init_name( e, name ) -- These includes ensure that the 'make standalone' process -- copies the libararies to the standalone folder Include( "utillib.lua" ) Include( "quatlib.lua" ) Include( "physlib.lua" ) -- save name to local list names[ e ] = name -- null the list entries for this entity so that when -- main routine called for the first time they will be -- correctly initialised dimensions[ g_Entity[e].obj ] = nil pEnt[ e ] = nil -- load sprites if not already loaded if Sprites[ 'hand1' ] == nil then Sprites[ 'hand1' ] = CreateSprite(LoadImage ( "scriptbank\\pickuppable\\hand1.png" ) ) Sprites[ 'hand2' ] = CreateSprite(LoadImage ( "scriptbank\\pickuppable\\hand2.png" ) ) local s = Sprites[ 'hand1' ] SetSpriteOffset( s, -1 , handSize / 2 ) SetSpriteSize ( s, -1 , handSize ) SetSpriteDepth ( s, 0 ) HideHand( 'hand1' ) s = Sprites[ 'hand2' ] SetSpriteOffset( s, -1 , handSize / 2 ) SetSpriteSize ( s, -1 , handSize ) SetSpriteDepth ( s, 0 ) HideHand( 'hand2' ) end -- if there is global maximum weight/size use it to initialise the -- local copy if g_pickuppables_list.maxWeight ~= nil then maxWeight = g_pickuppables_list.maxWeight end if g_pickuppables_list.maxSize ~= nil then maxSize = g_pickuppables_list.maxSize end if g_pickuppables_list.pushEnabled ~= nil then pushEnabled = g_pickuppables_list.pushEnabled end if g_pickuppables_list.throwEnabled ~= nil then throwEnabled = g_pickuppables_list.throwEnabled end end local function getRealWorldYangle( xa, ya, za ) -- Euler in degrees, returns radians -- Tricky to explain but basically g_PlayerAngY is the real-world -- angle, i.e. 0-359 degrees, whereas the Euler angle for an entity -- returned by GetObjectPosAng is not. -- In order to get the 'real-world' equivalent we need to do some math. -- first rotate a unit 'facing forward' vector by the Euler angles local xv, _, zv = U.Rotate3D( 0, 0, 1, rad( xa ), rad( ya ), rad ( za ) ) -- now work out the angle from the x and z components of the result return atan( xv, zv ) end local function isLevelIsh( xa, ya, za ) -- Euler in degrees, returns true/false local maxAng = 180 - levelAng local minAng = -180 + levelAng return ( xa < minAng or xa > maxAng or ( xa < levelAng and xa > -levelAng ) ) and ( za < minAng or za > maxAng or ( za < levelAng and za > -levelAng ) ) end local RayCast = IntersectAll -- given angles and origin position return centre position local function getCentre( p, x, y, z, xa, ya, za ) -- first rotate offsets local dims = dimensions[ p.obj ] local xo, yo, zo = U.Rotate3D( dims.cx, dims.cy, dims.cz, xa, ya, za ) -- now calculate centre of object return x + xo, y + yo, z + zo end local function checkIfSatOn( pb, bpx, bpy, bpz, bax, bay, baz, pt, tpx, tpy, tpz, tax, tay, taz ) -- can't be stacked if its origin is lower than the base -- objects origin no matter what the relative orientations are if tpy < bpy then return false end -- send a ray trace straight down from the centre of the top object -- first calculate centre of object local tcx, tcy, tcz = getCentre( pt, tpx, tpy, tpz, tax, tay, taz ) local lowestY = tcy - ( dimensions[ pt.obj ].h / 2 + 5 ) -- now cast rays from around centre point to try and find the base object local bObj = RayCast( tcx, tcy, tcz, tcx, lowestY , tcz, pt.obj ) if bObj ~= pb.obj then bObj = RayCast( tcx + 1, tcy, tcz + 1, tcx + 1, lowestY, tcz + 1, pt.obj ) end if bObj ~= pb.obj then bObj = RayCast( tcx + 1, tcy, tcz - 1, tcx + 1, lowestY, tcz - 1, pt.obj ) end if bObj ~= pb.obj then bObj = RayCast( tcx - 1, tcy, tcz + 1, tcx + 1, lowestY, tcz + 1, pt.obj ) end if bObj ~= pb.obj then bObj = RayCast( tcx - 1, tcy, tcz - 1, tcx + 1, lowestY, tcz - 1, pt.obj ) end if bObj ~= pb.obj then return false end local q1 = Q.Conjugate( Q.FromEuler( bax, bay, baz ) ) local xo, yo, zo = U.Rotate3D( tpx - bpx, tpy - bpy, tpz - bpz, Q.ToEuler( q1 ) ) return true, xo, yo, zo, Q.Mul( q1 , Q.FromEuler( tax, tay, taz ) ) end local function buildStackList( p, base_obj ) -- basically we have to find all pickuppable objects -- that are sitting on this one and bung them in a -- separate list, storing their positions relative to -- the base entity and rotations relative to it -- we have to do this before we first move the object -- so we can also move all the others at the same time local stacked = "No" -- first check if object is roughly level local pbx, pby, pbz, abx, aby, abz = GetObjectPosAng( p.obj ) -- for now only allow stacking on 'level(ish)' object if p.obj == base_obj and not isLevelIsh( abx, aby, abz ) then p.stackList = nil return end -- roughly level so lets find out who is sat on us; -- start by creating an empty list, we can delete -- it if we don't find any 'stackable' items p.stackList = {} for k, v in pairs( pEnt ) do -- don't process ourselves! if v ~= p then -- only process entities within range local tpx, tpy, tpz, tax, tay, taz = GetObjectPosAng( v.obj ) if U.CloserThan( tpx, tpy, tpz, pbx, pby, pbz, maxSize * 2 ) then local isOn, ox, oy, oz, q = checkIfSatOn( p, pbx, pby, pbz, rad(abx), rad(aby), rad(abz), v, tpx, tpy, tpz, rad(tax), rad(tay), rad(taz), dimensions[ k ] ); if isOn then -- offset values are from the centres not origin! p.stackList[ k ] = { obj = v.obj, x = ox, y = oy, z = oz, q = q } stacked = "Yes" end end end end if stacked == "Yes" then for k, v in pairs( p.stackList ) do -- now recurse buildStackList( pEnt[ k ] ) end else -- didn't find any so delete the stack p.stackList = nil end end local function processRotation( e, p, plQ ) local yaw = -( g_MouseX - 50 ) / 1000 local pitch = -( g_MouseY - 50 ) / 1000 if abs( yaw ) < 0.01 then yaw = 0 end if p.stackList ~= nil or abs( pitch ) < 0.01 then pitch = 0 end if promptsOn then Prompt( "Use mouse to rotate " .. names[ e ] ) end if roty ~= 0 or rotx ~= 0 then local quat = Q.FromEuler( pitch, yaw, 0 ) p.quat = Q.Mul( quat, p.quat ) end end local function clearStacks( p ) if p.stackList ~= nil then for k, _ in pairs( p.stackList ) do clearStacks( pEnt[ k ] ) pEnt[ k ].stackList = nil end end end local topSpeed = 0 local dropTimer = math.huge local function dropObject( p, e ) droppedEnt = e mouse1Clicked = true carryingEnt = nil mouseTimer = g_Time + 200 -- reset quat ready for next time we pick this entity up -- stacking will use its own calculations p.quat = nil -- clear stackList(s) clearStacks( p ) p.stackList = nil SetGamePlayerControlTopspeed( topSpeed ) dropTimer = g_Time + 1000 end local corners = {} local function adjustForTerrainCollision( nx, ny, nz, dims, ax, ay, az ) -- We need to work out the coordinates of each 'corner' and check if -- any of them would be under the terrain, if so we need to raise y -- by the difference plus a little bit -- ( could be expanded to add a raycast from corner to corner to check -- for a terrain collision, but that's difficult ;-) ) -- TODO - replace this with the new terrain collision detection commands -- at some point local hw, hh, hl = dims.w / 2, dims.h / 2, dims.l / 2 -- rotate offsets local ofx, ofy, ofz = U.Rotate3D( dims.cxs, dims.cys,dims.czs, ax, ay, az ); -- create our own collision box corners = { { xo = hw, yo = hh, zo = hl }, { xo = hw, yo = hh, zo = -hl }, { xo = hw, yo = -hh, zo = hl }, { xo = hw, yo = -hh, zo = -hl }, { xo = -hw, yo = hh, zo = hl }, { xo = -hw, yo = hh, zo = -hl }, { xo = -hw, yo = -hh, zo = hl }, { xo = -hw, yo = -hh, zo = -hl } }; local heightDiff = 0 -- now check all the corners to see if any are below -- the terrain for k, v in pairs( corners ) do -- rotate offsets to entity angle local ox, oy, oz = U.Rotate3D( v.xo, v.yo, v.zo, ax, ay, az ); local th = GetTerrainHeight( nx + ox + ofx , nz + oz + ofz ) local yh = ny + oy + ofy if th > yh and th - yh > heightDiff then heightDiff = th - yh end end return ny + heightDiff end local function rePositionEnt( e, obj, x, y, z, ax, ay, az ) CollisionOff( e ) PositionObject( obj , x, y, z ) RotateObject( obj, deg( ax ), deg( ay ), deg( az ) ) CollisionOn( e ) end local function playerIsOn( p, ppx, ppy, ppz ) return p.obj == RayCast( ppx, ppy, ppz, ppx, ppy - 200, ppz, 0 ) end local function repositionStack( p, nquat, nx, ny, nz, ppx, ppy, ppz ) if p.stackList ~= nil then if ppy > nx + 20 or playerIsOn( p, ppx, ppy, ppz ) then dropObject( pEnt[ carryingEnt ], carryingEnt ) return end -- quaternion of base item local xa, ya, za = Q.ToEuler( nquat ) for k, v in pairs( p.stackList ) do -- calculate relative rotation local pquat = Q.Mul( nquat, v.q ) -- rotate centre offsets to match new angle local xo, yo, zo = U.Rotate3D( v.x, v.y, v.z, xa, ya, za ) local px, py, pz = nx + xo, ny + yo, nz + zo local nxa, nya, nza = Q.ToEuler( pquat ) rePositionEnt( k, v.obj, px, py, pz, nxa, nya, nza ) repositionStack( pEnt[ k ], pquat, px, py, pz, ppx, ppy, ppz ) if carryingEnt == nil then return end end end end local SEPTT_timer = 0 local function stopEntsPunchingThroughTerrain() -- or more accurately rescue them when they have been punched through if g_Time > SEPTT_timer then -- check 5 times a second SEPTT_timer = g_Time + 200 for k, v in pairs( pEnt ) do if k ~= carryingEnt then local x, y, z, xa, ya, za = GetObjectPosAng( v.obj ) -- check if the centre of the object is below terrain height local cx, cy, za = getCentre( v, x, y, z, xa, ya, za ) local th = GetTerrainHeight( x, z ) if cy < th - 30 then ya = getRealWorldYangle( xa, ya, za ) rePositionEnt( k, v.obj, x, th + 1, z, 0, ya, 0 ) end end end end end local numFrames = 7 local function positionHandler( e, p, rq, ppx, ppy, ppz ) -- So we need this to run every frame and do the following: if playerIsOn( p, ppx, ppy, ppz ) then dropObject( p, e ) return end -- calculate current object position and rotation : cp, cq local cpx, cpy, cpz, cax, cay, caz = GetObjectPosAng( p.obj ) local cq = Q.FromEuler( rad( cax ), rad( cay ), rad( caz ) ) -- calculate difference between cp and required position : rp local xtg, ytg, ztg = p.rx - cpx, p.ry - cpy, p.rz - cpz -- if difference greater that some amount work out how far we need -- to move and calculate a per frame vector to apply which will depend -- on maximum movement allowable local dtg = xtg*xtg + ytg*ytg + ztg*ztg if dtg < 1 then rePositionEnt( e, p.obj, p.rx, p.ry, p.rz, Q.ToEuler( rq ) ) repositionStack( p, rq, p.rx, p.ry, p.rz, ppx, ppy, ppz ) else xtg, ytg, ztg = xtg / numFrames, ytg / numFrames, ztg / numFrames -- calculate per frame rotation (SLERP) : sq (same rules as for position) local sq = Q.NLerp( cq, rq, 1 / numFrames ) local nx, ny, nz = cpx + xtg, cpy + ytg, cpz + ztg rePositionEnt( e, p.obj, nx, ny, nz, Q.ToEuler( sq ) ) -- now position stacked ents repositionStack( p, sq, nx, ny, nz, ppx, ppy, ppz ) end end local RayCast = IntersectAll local throwTimer = 0 local hitObject = {} local function CheckThrowCollision() if throwTimer == 0 then throwTimer = g_Time + 1000 end local colList = P.GetObjectCollisionDetails( thrownObj ) if colList ~= nil then for i, v in ipairs( colList ) do if v.obj ~= 0 then local force = v.f * 1000 local e = P.ObjectToEntity( v.obj ) local Ent = g_Entity[ e ] if Ent ~= nil then SetEntityHealth( e, g_Entity[ e ].health - force ) end if PU_ShowDecal ~= nil then PU_ShowDecal( "PU Decal", v.x, v.y, v.z, force * 5 ) end end end -- remove health from thrown object causing it to -- explode ( if explodable ) SetEntityHealth( P.ObjectToEntity( thrownObj ), 0) elseif g_Time < throwTimer then -- didn't hit anything yet so give it more time return end -- done with this object so remove from checking list RemoveObjectCollisionCheck( thrownObj ) thrownObj = nil throwTimer = 0 end local function HandleTerrainCollisions() local colList = P.GetTerrainCollisionDetails( thrownObj ) if colList == nil then return end for _, v in ipairs( colList ) do if v.x ~= 0 then if PU_ShowDecal ~= nil then PU_ShowDecal( "PU Decal2", v.x, v.y, v.z ) end end end end local currSpeed = 0 local colCheckTimer = 0 ----------------------------------------------------- -- Start of main entry routine - called very frame -- ----------------------------------------------------- function pickuppable_main(e) if throwEnabled and P.ObjectToEntity( thrownObj ) == e then if g_Time > colCheckTimer then HandleTerrainCollisions() CheckThrowCollision() colCheckTimer = g_Time + 100 -- 1/10 second- end end if g_pickuppables_list.removed ~= nil and g_pickuppables_list.removed[ e ] then return end local Ent = g_Entity[ e ] if Ent == nil then return end -- first time though calculate dimensions of entity local dims = dimensions[ Ent.obj ] if dims == nil then getDims( Ent.obj ) return end local p = pEnt[ e ] if p == nil then -- store the object id for future use pEnt[ e ] = { obj = Ent.obj } return end if carryingEnt == e or droppedEnt == e then stopEntsPunchingThroughTerrain() -- delayed re-enabling of jump mode if g_Time > dropTimer then SetGamePlayerControlJumpMode( 0 ) dropTimer = math.huge end end local pObj, objX, objY, objZ = 0, 0, 0, 0 if carryingEnt == nil then if not U.PlayerCloserThan( e, 200 ) then return end pObj, objX, objY, objZ = P.ObjectPlayerLookingAt( 100 ) if pObj == p.obj then if handShowing == 'none' then ShowHand( 'hand1' ) end elseif not ObjectPickuppable( pObj ) then if handShowing ~= 'none' then HideHand( handShowing ) end return else return end elseif carryingEnt == e then if g_PlrKeySPACE == 1 then SetGamePlayerControlTopspeed( minSpeed ) else SetGamePlayerControlTopspeed( currSpeed ) end -- get player position and angles local ppX, ppY, ppZ = g_PlayerPosX, g_PlayerPosY, g_PlayerPosZ local paX, paY, paZ = rad( g_PlayerAngX ), rad( g_PlayerAngY ), rad( g_PlayerAngZ ); -- player quaternion local plQ = Q.FromEuler( 0, paY, paZ ) local Obj = P.ObjectPlayerLookingAt( 100 ) if Obj == p.obj then -- we are carrying this entity if handShowing ~= 'none' then HideHand( handShowing ) end if promptsOn then if not throwEnabled then Prompt( "LMB to drop " .. names[ e ] .. ", Hold RMB to rotate") else Prompt( "LMB(+E) to drop(throw) " .. names[ e ] .. ", Hold RMB to rotate") end end if g_MouseClick == 1 or GetPlayerWeaponID() ~= 0 then if not mouse1Clicked then dropObject( p, e ) if not throwEnabled then return end -- handle throwing if g_KeyPressE == 1 and g_MouseClick == 1 then local force = min( maxWeight / 6, dims.m ) vx, vy, vz = U.Rotate3D ( 0, 0, 1, paX, paY, paZ ) if names[ e ] == 'saw blade' then force = force * 20 PushObject( p.obj, vx * force, vy * force, vz * force, rnd()/10 , 0, rnd()/10 ) else PushObject( p.obj, vx * force, vy * force, vz * force, rnd()/100, rnd()/100, rnd()/100 ) end -- test new collision testing command thrownObj = p.obj AddObjectCollisionCheck( p.obj ) -- do initial check in case enemy really close! CheckThrowCollision() end return end elseif g_MouseClick == 2 then if not mouse2Clicked then mouse2Clicked = true ActivateMouse() end processRotation( e, p, plQ ) elseif mouse2Clicked or (mouse1Clicked and g_Time > mouseTimer) then DeactivateMouse() mouse1Clicked = false mouse2Clicked = false mouseTimer = math.huge end end -- move entity with player -- (may need to handle collision as entity will probably be -- outside player capsule) if p.quat == nil then -- we've just picked it up so initialise: -- work out offset position of entity when held by player local size = max( dims.w, dims.h, dims.l ) -- need to ensure entity is offset far enough to rotate p.xo, p.yo, p.zo = carryOfst, carryHght, carryDist + size / 2 -- get entity angles local _,_,_, eaX, eaY, eaZ = GetObjectPosAng( p.obj ) -- need to work out real world angle of entity rwY = getRealWorldYangle( eaX, eaY, eaZ ) -- store angle as quaternion p.quat = Q.FromEuler( 0, rwY - paY, 0 ) end -- work out offset position of entity when held by player -- first taking account of player angles local xo, yo, zo = U.Rotate3D( p.xo, p.yo, p.zo, paX, paY, paZ ) -- work out rotation quaternion of entity local enQ = Q.Mul( plQ, p.quat ) -- then object offsets, i.e to centre it properly local oAx, oAy, oAz = Q.ToEuler( enQ ) local oxo, oyo, ozo = U.Rotate3D( dims.cx, dims.cy, dims.cz, oAx, oAy, oAz ) -- calculate new position for entity p.rx, p.ry, p.rz = ppX + xo - oxo, ppY + yo - oyo, ppZ + zo - ozo -- make sure we don't sink the entity into the terrain p.ry = adjustForTerrainCollision( p.rx, p.ry, p.rz, dims, oAx, oAy, oAz ) positionHandler( e, p, enQ, ppX, ppY, ppZ ) return else -- something being carried and it isn't us so leave return end if GetPlayerWeaponID() ~= 0 then if promptsOn then Prompt( "Can't manipulate " .. names[ e ] .. " without your hands free!" ) end if handShowing == 'hand1' then HideHand( handShowing ) ShowHand( 'hand2' ) end mouseTimer = g_Time + 200 return end -- if we've got here we can potentially pick up the item -- check how heavy it is if max( dims.w, dims.h, dims.l ) > maxSize then if promptsOn then Prompt( names[ e ] .. " is too large to carry!" ) end if handShowing == 'hand1' then HideHand( handShowing ) ShowHand( 'hand2' ) end elseif dims.m > maxWeight then if promptsOn then Prompt( names[ e ] .. " is too heavy to lift!" ) end if handShowing == 'hand1' then HideHand( handShowing ) ShowHand( 'hand2' ) end else if handShowing == 'hand2' and g_Time > mouseTimer then HideHand( handShowing ) ShowHand( 'hand1' ) end if mouse1Clicked then if g_Time > mouseTimer then mouse1Clicked = false mouseTimer = math.huge end return end if promptsOn then if pushEnabled then Prompt( "LMB to pick up " .. names[ e ] .. ", E/R + LMB to Push/Pull." ) else Prompt( "LMB to pick up " .. names[ e ] ) end end local pushX, pushY, pushZ = P.GetViewVector() -- PromptLocal( e, pushX .. "," .. pushY .. "," .. pushZ .. " : " .. -- objX .. "," .. objY .. "," .. objZ ) if g_MouseClick == 1 then if pushEnabled then -- handle pushing if g_KeyPressE == 1 or g_KeyPressR == 1 then -- local pushX, pushY, pushZ = P.GetViewVector() -- get object position and angle local ex, ey, ez, xa, ya, za = GetObjectPosAng( p.obj ) -- work out centre of object local ox, oy, oz = U.Rotate3D( dims.cx, dims.cy, dims.cz, xa, ya, za ); local cx, cy, cz = ex + ox, ey + oy, ez + oz -- finally work out contact point offsets from centre ox, oy, oz = objX - cx, objY - cy, objZ - cz -- push force less than throw force local force if g_KeyPressE == 1 then force = min( maxWeight / 20, dims.m / 4 ) -- and push! PushObject( p.obj, pushX*force, pushY*force, pushZ*force, ox, oy, oz ) else -- pull force less than push force local force = min( maxWeight / 60, dims.m / 6 ) -- and pull! PushObject( p.obj, -pushX*force, -pushY*force, -pushZ*force, ox, oy, oz ) end return end end mouse1Clicked = true -- build stack list buildStackList( p, p.obj ) -- check how heavy the stack is local currWeight = stackWeight( p ) + dims.m if currWeight > maxWeight then if promptsOn then PromptDuration( "Stack is too heavy, remove some items or get stronger!", 2000 ) end if handShowing == 'hand1' then HideHand( handShowing ) ShowHand( 'hand2' ) end mouseTimer = g_Time + 2000 else carryingEnt = e mouseTimer = g_Time + 200 topSpeed = GetGamePlayerControlTopspeed() currSpeed = minSpeed + ( ( maxWeight - currWeight ) / ---------------------- maxWeight ) * ( topSpeed - minSpeed ) SetGamePlayerControlTopspeed( currSpeed ) SetGamePlayerControlJumpMode( 3 ) end end end end