Re-use of animation pushable block?

Ask for help about creating mods and scripts for Grimrock 2 or share your tips, scripts, tools and assets with other modders here. Warning: forum contains spoilers!
User avatar
THOM
Posts: 1281
Joined: Wed Nov 20, 2013 11:35 pm
Location: Germany - Cologne
Contact:

Re-use of animation pushable block?

Post by THOM »

It seems, the pushable block elements are hardcoded. At least I can't find them in the asset pack.

For sure, one can find the object definition, and the commands in the scripting reference, but not the animation- and sound definition. Especially the action "push" is nowhere declared.

I would like to re-use the pushable block concept - but in a complete different environment. Therefore it would be helpful to get a hand on that definitions to alter them. For sure it is possible to change the behaviour with some tweaks, but this has a it's limits.

Does someone has an idea how to do that?
THOM formaly known as tschrage
_______________________________________________
My MOD (LoG1): Castle Ringfort Thread
My MOD (LoG2): Journey To Justice Thread | Download
minmay
Posts: 2790
Joined: Mon Sep 23, 2013 2:24 am

Re: Re-use of animation pushable block?

Post by minmay »

There's no animation involved with pushable blocks. The PushableBlockComponent just sets world position/rotation every frame for the hovering effect and pushing.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
User avatar
Duncan1246
Posts: 404
Joined: Mon Jan 19, 2015 7:42 pm

Re: Re-use of animation pushable block?

Post by Duncan1246 »

THOM wrote:I would like to re-use the pushable block concept - but in a complete different environment.
Does someone has an idea how to do that?
I am not sure of what you have in mind with "completely different environment" but it's easy to replace the model by another in the definition, so you can push absolutely anything you want.
The Blue Monastery (LOG1)
download at:http://www.nexusmods.com/grimrock/mods/399/?

Finisterrae(LOG2)
download at:http://www.nexusmods.com/legendofgrimrock2/mods/61/?
Azel
Posts: 808
Joined: Thu Nov 06, 2014 10:40 pm

Re: Re-use of animation pushable block?

Post by Azel »

Consider the code used in the "Elevator" demo: http://www.nexusmods.com/legendofgrimrock2/mods/10/?

I imagine that you would be doing something similar, only moving horizontally instead of vertically :mrgreen:
User avatar
THOM
Posts: 1281
Joined: Wed Nov 20, 2013 11:35 pm
Location: Germany - Cologne
Contact:

Re: Re-use of animation pushable block?

Post by THOM »

Thank you all.

I will have a closer look at the elevator definition - maybe that's it...

@Duncan1246: I know that I can replace the model of the block by something else. But then you are alway connected to sound and floating animation of the block. If you want something different - and maybe more than one replacement, it becomes tricky.

But probably I have the solution now.
THOM formaly known as tschrage
_______________________________________________
My MOD (LoG1): Castle Ringfort Thread
My MOD (LoG2): Journey To Justice Thread | Download
User avatar
THOM
Posts: 1281
Joined: Wed Nov 20, 2013 11:35 pm
Location: Germany - Cologne
Contact:

Re: Re-use of animation pushable block?

Post by THOM »

*sigh*

I am still trying to get my move-animation to work
what I got so far is a script-component in the object whose function will be triggered by a timer-component of the object.
Result of my script is, that the object is not moved until it has reached the moddle of the next grid-square but until the end of the whole map...

obviously I am doing something wrong with the triggering and/or the computing of the coordinates. but what?

Code: Select all

		function moveCart()
      			local lx = self.go.x
      			local ly = self.go.y

				if party.facing == 0 then
					ly = ly -0.01
					if ly > self.go.y -1 then 
						self.go:setPosition(self.go.x, self.go.y-ly, self.go.facing, self.go.elevation, self.go.level)
					else
						self.go.movetimer:stop()
					end
				end
				if party.facing == 1 then
					lx = lx +0.01
					if lx < self.go.x +1 then 
						self.go:setPosition(self.go.x+lx, self.go.y, self.go.facing, self.go.elevation, self.go.level)
					else
						self.go.movetimer:stop()
					end
				end
				if party.facing == 2 then
					ly = ly +0.01
					if ly < self.go.y +1 then 
						self.go:setPosition(self.go.x, self.go.y+ly, self.go.facing, self.go.elevation, self.go.level)
					else
						self.go.movetimer:stop()
					end
				end
				if party.facing == 3 then
					lx = lx -0.01
					if lx > self.go.x -1 then 
						self.go:setPosition(self.go.x-lx, self.go.y, self.go.facing, self.go.elevation, self.go.level)
					else
						self.go.movetimer:stop()
					end
				end
    			 end
THOM formaly known as tschrage
_______________________________________________
My MOD (LoG1): Castle Ringfort Thread
My MOD (LoG2): Journey To Justice Thread | Download
minmay
Posts: 2790
Joined: Mon Sep 23, 2013 2:24 am

Re: Re-use of animation pushable block?

Post by minmay »

Lots of problems with this.
1. Using non-integer values for map position. You should use setWorldPosition() for that.
2. Not accounting for variable framerate. You should use Time.deltaTime() to move the object by the correct amount each frame.
3. Using party facing when it can clearly change in the middle of everything.

This is my object transformation code atm, lots of stuff is unfinished (rotation is unfinished, callbackpos in moveObjectDistance is unimplemented, callbacks are not called in the correct order) but it will accomplish what you are trying to do. The function you're interested in is moveObjectDistance(). You need to call update() once every frame; just use a TimerComponent on the party object with an interval like 0.00001, since timers only trigger once per frame at most.
The only functions you should call externally are:
- the functions in the "Simple stuff" section
- update() ONCE every frame
- translate(), rotate(), and scale() if you want instant translation/rotation/scaling
- moveObjectDistance() and rotateAngles() if you want smooth translation/rotation

Code: Select all

-- Get rotation matrices that we can use for temporary storage. Here are some
-- properties of rotation matrices in Grimrock that you may not expect:
-- - GameObject:getWorldRotation() returns a COPY.
-- - According to a grimrock.net blog post, there's a "pool" of matrices so that
--   new ones aren't constantly being allocated. 
-- To get away with these two matrices, we must follow a specific discipline:
-- assume scratchMatrix is invalidated after you call any function EXCEPT
--	for matrixMultiply.
scratchMatrix = {x={},y={},z={},w={}}
do
  local s = self.go:getWorldRotation()
	for c=1,4 do
		scratchMatrix.x[c] = s.x[c]
		scratchMatrix.y[c] = s.y[c]
		scratchMatrix.z[c] = s.z[c]
		scratchMatrix.w[c] = s.w[c]
	end
end

--------------------------------------------------------------------------------
-- SIMPLE STUFF (spawn, destroy, set non-world position, etc.)
--------------------------------------------------------------------------------

-- spawn entity name at same position as on with id id
function spawnOn(on,name,id)
	return spawn(name,on.level,on.x,on.y,on.facing,on.elevation,id)
end

-- spawn entity name at same position *and* world position as on with id id
function spawnOnExact(on,name,id)
	local spawned = spawn(name,on.level,on.x,on.y,on.facing,on.elevation,id)
	spawned:setWorldPosition(on:getWorldPosition())
	return spawned
end

-- spawn entity name at same position *and* world position *and* world rotation as on with id id
function spawnOnExactWithRotation(on,name,id)
	local spawned = spawn(name,on.level,on.x,on.y,on.facing,on.elevation,id)
	spawned:setWorldPosition(on:getWorldPosition())
	spawned:setWorldRotation(on:getWorldRotation())
	return spawned
end

-- set world position taking a table instead of a vector
function setWorldPositionTable(gameObject,position)
	gameObject:setWorldPosition(vec(position[1],position[2],position[3],position[4] or 0))
end

-- a version of setDoorState that...actually works
-- TODO: obsolete in 2.1.19, lol
function safeSetDoorState(gameObject,doorComponentName,state)
	local door = gameObject[doorComponentName]
	if state == "closed" then
		local closeSound, closeVelocity, lockSound = door:getCloseSound(), door:getCloseVelocity(), door:getLockSound()
		door:setCloseSound("silence") door:setCloseVelocity(-math.huge) door:setLockSound("silence")
		door.go.controller:close()
		game.frame.callNextFrame(safeSetDoorStateCleanup,{gameObject.id,doorComponentName,state,closeSound,closeVelocity,lockSound})
	elseif state == "open" then
		local openSound, openVelocity, lockSound = door:getOpenSound(), door:getOpenVelocity(), door:getLockSound()
		door:setOpenSound("silence") door:setOpenVelocity(math.huge) door:setLockSound("silence")
		door.go.controller:open()
		game.frame.callNextFrame(safeSetDoorStateCleanup,{gameObject.id,doorComponentName,state,openSound,openVelocity,lockSound})
	else
		debug.exceptions.err("invalid door state ("..state..") in safeSetDoorState for "..gameObject.id.."."..doorComponentName)
	end
end

function safeSetDoorStateCleanup(gameObjectId, doorComponentName, state, sound, velocity, lockSound)
	local door = findEntity(gameObjectId)[doorComponentName]
	door:setLockSound(lockSound)
	if state == "closed" then
		door:setCloseSound(sound) door:setCloseVelocity(velocity)
	elseif state == "open" then
		door:setOpenSound(sound) door:setOpenVelocity(velocity)
	else
		debug.exceptions.err("invalid door state ("..state..") in safeSetDoorStateCleanup for "..gameObjectId.."."..doorComponentName)
	end
end

function setFacing(gobj,facing)
	gobj:setPosition(gobj.x,gobj.y,facing,gobj.elevation,gobj.level)
end
function setX(gobj,x)
	gobj:setPosition(x,gobj.y,gobj.facing,gobj.elevation,gobj.level)
end
function setY(gobj,y)
	gobj:setPosition(gobj.x,y,gobj.facing,gobj.elevation,gobj.level)
end
function setElevation(gobj,e)
	gobj:setPosition(gobj.x,gobj.y,gobj.facing,e,gobj.level)
end
-- like setPosition but leave arguments nil to keep original position
function setPositionPartial(gobj,x,y,facing,elevation,level)
	gobj:setPosition(x or gobj.x,y or gobj.y,facing or gobj.facing,elevation or gobj.elevation,level or gobj.level)
end

-- call gobj:destroy()
function destroy(gobj)
	gobj:destroy()
end

-- call gobj:destroy() and return true if gobj is not nil or false
-- do nothing and return false if gobj is nil or false
function safeDestroy(gobj)
	if gobj then
		gobj:destroy()
		return true
	else
		return false
	end
end

--------------------------------------------------------------------------------
-- TRANSFORMATION
--------------------------------------------------------------------------------

-- These functions are aimed at translating, rotating, and scaling GameObjects.
-- They change only world position and world rotation.
-- Performance here is not especially good, particularly for rotation, so don't
-- use on too many objects at once.

X = 1
Y = 2
Z = 3
W = 4

-- Table of moving GameObjects and corresponding data.
gameObjects = {}

-- Translate a game object's world position.
-- TODO: call callbacks in correct order.
function translate(gobj, x, y, z)
	local pos = gobj:getWorldPosition()
	local gid = gobj.id
	local gData = gameObjects[gid]
	local nx = pos[X]+(x or 0)
	local ny = pos[Y]+(y or 0)
	local nz = pos[Z]+(z or 0)
	if gData then
		local e = gData.endpoints
		local v = gData.velocities
		local c = gData.callbacks
		local callx = false
		local cally = false
		local callz = false
		if e then
			-- reaching an endpoint; velocityCleanup will handle the rest
			if e[X] and ((pos[X] <= e[X] and e[X] <= nx) or (pos[X] >= e[X] and e[X] >= nx)) then
				nx = e[X]
				if v then
					v[X] = nil
					if c and c[X] then
						callx = true
					end
				end
			end
			if e[Y] and ((pos[Y] <= e[Y] and e[Y] <= ny) or (pos[Y] >= e[Y] and e[Y] >= ny)) then
				ny = e[Y]
				if v then
					v[Y] = nil
					if c and c[Y] then
						cally = true
					end
				end
			end
			if e[Z] and ((pos[Z] <= e[Z] and e[Z] <= nz) or (pos[Z] >= e[Z] and e[Z] >= nz)) then
				nz = e[Z]
				if v then
					v[Z] = nil
					if c and c[Z] then
						callz = true
					end
				end
			end
		end
		gobj:setWorldPosition(vec(nx,ny,nz,pos[4]))
		if callx then
			c[X][1](gobj,c[X][2]) -- call callback with args
			c[X] = nil
		end
		if cally then
			c[Y][1](gobj,c[Y][2]) -- call callback with args
			c[Y] = nil
		end
		if callz then
			c[Z][1](gobj,c[Z][2]) -- call callback with args
			c[Z] = nil
		end
	else	-- if we dont have to worry about endpoints or callbacks, the code is somewhat shorter.
		gobj:setWorldPosition(vec(nx,ny,nz,pos[4]))
	end
end

-- Update moving objects. Should be called once per frame.
function update()
	local dtime = Time.deltaTime()
	for gobjId,data in pairs(gameObjects) do
		local d = {[X]=0,[Y]=0,[Z]=0}
		local ad = {[X]=nil,[Y]=nil,[Z]=nil}
		local v = data.velocities
		local a = data.accelerations
		local av = data.angularVelocities
		for dim = X, Z do
			if v and v[dim] then
				if a and a[dim] then
					-- In case you failed both physics and calculus, this formula will
					-- only work for constant acceleration.
					d[dim] = (v[dim]+0.5*a[dim]*dtime)*dtime
					v[dim] = v[dim]+a[dim]*dtime
				else
					d[dim] = v[dim]*dtime
				end
			end
			if av and av[dim] then
				ad[dim] = av[dim]*dtime
			end
		end
		if v then translate(findEntity(gobjId),d[X],d[Y],d[Z]) end
		if av then rotate(findEntity(gobjId),ad[X],ad[Y],ad[Z]) end
		velocityCleanup(gobjId)
	end
end

-- Here's where things get kind of sucky. I do not know of any way to create new
-- matrices other than acquiring them via GameObject:getWorldRotation(), which
-- creates a new copy of the matrix every time.
-- TODO: call callbacks in correct order
function rotate(gobj,dX,dY,dZ)
	local gid = gobj.id
	local gData = gameObjects[gid]

	local mat = gobj:getWorldRotation()
	local av = gData and gData.angularVelocities
	local adr = gData and gData.angularDistanceRemaining
	local c = gData and gData.angularCallbacks
	local callx = false
	local cally = false
	local callz = false
	if dX then
		if adr then
			local remaining = adr[X]
			if remaining then
				if (dX >= 0 and dY >= remaining) or (dY < 0 and dY <= remaining) then
					dX = remaining
					adr[X] = nil
					av[X] = nil
					if c and c[X] then
						callx = true
					end
				else
					adr[X] = remaining-dX
				end
			end
		end
		createRotationMatrixX(scratchMatrix,dX)
		matrixMultiply(mat,scratchMatrix)
	end
	if dY then
		if adr then
			local remaining = adr[Y]
			if remaining then
				if (dY >= 0 and dY >= remaining) or (dY < 0 and dY <= remaining) then
					dY = remaining
					adr[Y] = nil
					av[Y] = nil
					if c and c[Y] then
						cally = true
					end
				else
					adr[Y] = remaining-dY
				end
			end
		end
		createRotationMatrixY(scratchMatrix,dY)
		matrixMultiply(mat,scratchMatrix)
	end
	if dZ then
		if adr then
			local remaining = adr[Z]
			if remaining then
				if (dZ >= 0 and dZ >= remaining) or (dZ < 0 and dZ <= remaining) then
					dZ = remaining
					adr[Z] = nil
					av[Z] = nil
					if c and c[Z] then
						callz = true
					end
				else
					adr[Z] = remaining-dZ
				end
			end
		end
		createRotationMatrixZ(scratchMatrix,dZ)
		matrixMultiply(mat,scratchMatrix)
	end
	gobj:setWorldRotation(mat)
	if callx then
		c[X][1](gobj,c[X][2]) -- call callback with args
		c[X] = nil
	end
	if cally then
		c[Y][1](gobj,c[Y][2]) -- call callback with args
		c[Y] = nil
	end
	if callz then
		c[Z][1](gobj,c[Z][2]) -- call callback with args
		c[Z] = nil
	end
end

function scale(gobj,dX,dY,dZ)
	local gid = gobj.id
	local gData = gameObjects[gid]

	local mat = gobj:getWorldRotation()
	scratchMatrix.x[1] = dX
	scratchMatrix.x[2] = 0
	scratchMatrix.x[3] = 0
	scratchMatrix.x[4] = 0
	scratchMatrix.y[1] = 0
	scratchMatrix.y[2] = dY
	scratchMatrix.y[3] = 0
	scratchMatrix.y[4] = 0
	scratchMatrix.z[1] = 0
	scratchMatrix.z[2] = 0
	scratchMatrix.z[3] = dZ
	scratchMatrix.z[4] = 0
	scratchMatrix.w[1] = 0
	scratchMatrix.w[2] = 0
	scratchMatrix.w[3] = 0
	scratchMatrix.w[4] = 1
	matrixMultiply(mat,scratchMatrix)
	
	gobj:setWorldRotation(mat)
end

-- Set angular velocity of an object. x/y/z can be nil or 0 to leave alone.
function setAngularVelocity(gobj,x,y,z)
	local gobjId = gobj.id
	if not gameObjects[gobjId] then gameObjects[gobjId] = {} end
	local gData = gameObjects[gobjId]
	if not gData.angularVelocities then
		local hasX = x and x ~= 0
		local hasY = y and y ~= 0
		local hasZ = z and z ~= 0
		if hasX or hasY or hasZ then
			gData.angularVelocities = {
				[X]=hasX and x,
				[Y]=hasY and y,
				[Z]=hasZ and z
				}
		end
	else
		gData.angularVelocities[X] = x
		gData.angularVelocities[Y] = y
		gData.angularVelocities[Z] = z
	end
end

-- Set angular distance remaining of an object. x/y/z can be nil or 0 to leave alone.
function setAngularDistanceRemaining(gobj,x,y,z)
	local gobjId = gobj.id
	local gData = gameObjects[gobjId]
	if not gData.angularDistanceRemaining then
		local hasX = x and x ~= 0
		local hasY = y and y ~= 0
		local hasZ = z and z ~= 0
		if hasX or hasY or hasZ then
			gData.angularDistanceRemaining = {
				[X]=hasX and x,
				[Y]=hasY and y,
				[Z]=hasZ and z
				}
		end
	else
		gData.angularDistanceRemaining[X] = x
		gData.angularDistanceRemaining[Y] = y
		gData.angularDistanceRemaining[Z] = z
	end
end

function createRotationMatrixX(mat, angle)
	local sin = math.sin(angle)
	local cos = math.cos(angle)
	mat.x[1] = 1
	mat.x[2] = 0
	mat.x[3] = 0
	mat.y[1] = 0
	mat.y[2] = cos
	mat.y[3] = sin
	mat.z[1] = 0
	mat.z[2] = -sin
	mat.z[3] = cos
	--return mat
end

function createRotationMatrixY(mat, angle)
	local sin = math.sin(angle)
	local cos = math.cos(angle)
	mat.x[1] = cos
	mat.x[2] = 0
	mat.x[3] = -sin
	mat.y[1] = 0
	mat.y[2] = 1
	mat.y[3] = 0
	mat.z[1] = sin
	mat.z[2] = 0
	mat.z[3] = cos
	--return mat
end

function createRotationMatrixZ(mat, angle)
	local sin = math.sin(angle)
	local cos = math.cos(angle)
	mat.x[1] = cos
	mat.x[2] = sin
	mat.x[3] = 0
	mat.y[1] = -sin
	mat.y[2] = cos
	mat.y[3] = 0
	mat.z[1] = 0
	mat.z[2] = 0
	mat.z[3] = 1
	--return mat
end

-- multiply 4x4 matrices (or tables) mat1 and mat2, place result in mat1
-- mat2 will be unchanged
function matrixMultiply(mat1,mat2)
	-- XXX: I'm sure this can be optimized better, but it's Lua so using the
	-- "good" algorithms may not help.
	-- currently the final column is not copied since as soon as it's
	-- overwritten, the original is no longer needed; we could, of course,
	-- save on storage by making more assignments...
	-- These are intentionally permanent variables. Why? This function is
	-- called EXTREMELY often. (That's also why we don't use a table.) This
	-- code is virtually impossible to profile, but based on my limited
	-- knowledge of LuaJIT I am around 90% sure this is faster than using
	-- temporary variables in this case.
	scratchx1 = mat1.x[1]
	scratchx2 = mat1.x[2]
	scratchx3 = mat1.x[3]
	scratchy1 = mat1.y[1]
	scratchy2 = mat1.y[2]
	scratchy3 = mat1.y[3]
	scratchz1 = mat1.z[1]
	scratchz2 = mat1.z[2]
	scratchz3 = mat1.z[3]
	scratchw1 = mat1.w[1]
	scratchw2 = mat1.w[2]
	scratchw3 = mat1.w[3]
	for i = 1,4 do
		mat1.x[i] = scratchx1 * mat2.x[i]
			+ scratchx2 * mat2.y[i]
			+ scratchx3 * mat2.z[i]
			+ mat1.x[4] * mat2.w[i]
		mat1.y[i] = scratchy1 * mat2.x[i]
			+ scratchy2 * mat2.y[i]
			+ scratchy3 * mat2.z[i]
			+ mat1.y[4] * mat2.w[i]
		mat1.z[i] = scratchz1 * mat2.x[i]
			+ scratchz2 * mat2.y[i]
			+ scratchz3 * mat2.z[i]
			+ mat1.z[4] * mat2.w[i]
		mat1.w[i] = scratchw1 * mat2.x[i]
			+ scratchw2 * mat2.y[i]
			+ scratchw3 * mat2.z[i]
			+ mat1.w[4] * mat2.w[i]
	end
	--return mat1
end

-- Set velocity of an object. x/y/z can be nil to leave alone.
function setVelocity(gobj,x,y,z)
	local gobjId = gobj.id
	if not gameObjects[gobjId] then gameObjects[gobjId] = {} end
	local gData = gameObjects[gobjId]
	if not gData.velocities then
		local hasX = x and x ~= 0
		local hasY = y and y ~= 0
		local hasZ = z and z ~= 0
		if hasX or hasY or hasZ then
			gData.velocities = {
				[X]=hasX and x or nil,
				[Y]=hasY and y or nil,
				[Z]=hasZ and z or nil
				}
		end
	else
		gData.velocities[X] = x
		gData.velocities[Y] = y
		gData.velocities[Z] = z
		velocityCleanup(gobjId)
	end
end

-- Modify velocity of an object. x/y/z can be 0 to leave alone.
function modifyVelocity(gobj,x,y,z)
	local gobjId = gobj.id
	if not gameObjects[gobjId] then gameObjects[gobjId] = {} end
	local gData = gameObjects[gobjId]
	if not gData.velocities then
		local hasX = x ~= 0
		local hasY = y ~= 0
		local hasZ = z ~= 0
		if hasX or hasY or hasZ then
			gData.velocities = {
				[X]=hasX and x or nil,
				[Y]=hasY and y or nil,
				[Z]=hasZ and z or nil
				}
		end
	else
		gData.velocities[X] = (gData.velocities[X] or 0)+x
		gData.velocities[Y] = (gData.velocities[Y] or 0)+y
		gData.velocities[Z] = (gData.velocities[Z] or 0)+z
		velocityCleanup(gobjId)
	end
end

-- Move an object a certain distance on each axis with specified velocities,
-- canceling any existing movement *and canceling existing callbacks without calling them*.
-- gobj: object to move
-- move: table with info about how to move the object. Supports the following fields:
--   dx, dy, dz: distance to move each axis (can be nil)
--   vx, vy, vz: velocity to move each axis (sign should match corresponding distance)
--   callbackx, callbacky, callbackz: functions to call when finished moving on axis (can be nil), first argument passed will be the object
--   callbackxargs, callbackyargs, callbackzargs: additional arguments to pass to callbacks (can be nil)
--   callbackpos: function to call when object's map position changes (can be nil), i.e. it crosses a tile boundary, first argument passed will be the object,
--                then the object's *previous* x,y,elevation as the second, third, and fourth arguments.
--   callbackposargs: additional arguments to pass to callbackpos (can be nil)
function moveObjectDistance(gobj,move)
	local gobjId = gobj.id
	setVelocity(gobj,move.vx,move.vy,move.vz)
	setEndpointsDelta(gobj,move.dx,move.dy,move.dz)
	gameObjects[gobjId].accelerations = nil
	if (move.dx and move.callbackx) or (move.dy and move.callbacky) or (move.dz and move.callbackz) or (move.callbackpos) then
		gameObjects[gobjId].callbacks = {[X] = {move.callbackx,move.callbackxargs}, [Y] = {move.callbacky,move.callbackyargs}, [Z] = {move.callbackz,move.callbackzargs}, ["callbackpos"] = {move.callbackpos,move.callbackposargs}}
	else
		gameObjects[gobjId].callbacks = nil -- clear existing
	end
end

-- WARNING XXX TODO NOTE ETC: the whole rotation code is completely broken
-- TODO: make this not completely useless for multiple axes (the framerate will change the 
-- rotation as the axes are applied individually).
-- Rotate an object a certain distance (degrees) on each axis with specified velocities,
-- canceling any existing rotation.
-- Note that currently this is pretty inefficient, so don't rotate too many objects at once.
-- gobj: object to move
-- dx, dy, dz: angle to rotate each axis
-- tx, ty, tz: time for rotation on each axis to complete
-- callbackx, callbacky, callbackz: functions to call when finished moving on axis (can be nil), first argument passed will be the object
-- callbackxargs, callbackyargs, callbackzargs: additional arguments to pass to callbacks (can be nil)
function rotateAngles(gobj,dx,dy,dz,tx,ty,tz,callbackx,callbackxargs,callbacky,callbackyargs,callbackz,callbackzargs)
	local gobjId = gobj.id	
	local avx = nil
	local avy = nil
	local avz = nil
	if dx then avx = dx/tx end
	if dy then avy = dy/ty end
	if dz then avz = dz/tz end
	setAngularVelocity(gobj,avx,avy,avz)
	setAngularDistanceRemaining(gobj,dx,dy,dz)
	velocityCleanup(gobjId)
	if (dx and callbackx) or (dy and callbacky) or (dz and callbackz) then
		gameObjects[gobjId].angularCallbacks = {[X] = {callbackx,callbackxargs}, [Y] = {callbacky,callbackyargs}, [Z] = {callbackz,callbackzargs}}
	end
end

-- Set the endpoints of an object relative to its current position. Cancels
-- existing endpoints.
function setEndpointsDelta(gobj,dx,dy,dz)
	local pos = gobj:getWorldPosition()
	local points = {
		[X] = (dx and dx ~= 0 and pos[X]+dx) or nil,
		[Y] = (dy and dy ~= 0 and pos[Y]+dy) or nil,
		[Z] = (dz and dz ~= 0 and pos[Z]+dz) or nil
		}
	if not gameObjects[gobj.id] then
		gameObjects[gobj.id] = {endpoints = points}
	else
		gameObjects[gobj.id].endpoints = points
	end
end

-- remove 0 velocities and corresponding endpoints
-- if channel has no or 0 velocity and no acceleration, will be removed
-- if all channels removed, object is freed
function velocityCleanup(gobjId)
	local gData = gameObjects[gobjId]
	if gData and findEntity(gobjId) then
		local v = gData.velocities
		local a = gData.accelerations
		local av = gData.angularVelocities
		if av then
			local e = gData.angularDistanceRemaining
			for i = X, Z do
				if (not av[i]) or av[i] == 0 then
					av[i] = nil
					if e then e[i] = nil end
				end
			end
			if not (av[X] or av[Y] or av[Z]) then
				gData.angularVelocities = nil
				gData.angularDistanceRemaining = nil
				if not v then
					gameObjects[gobjId] = nil
				end
			end
		end
		if v and not a then
			local e = gData.endpoints
			for i = X, Z do
				if (not v[i]) or v[i] == 0 then
					v[i] = nil
					if e then e[i] = nil end
				end
			end
			if not (v[X] or v[Y] or v[Z]) then
				gData.velocities = nil
				if not gData.angularVelocities then
					gameObjects[gobjId] = nil
				end
			end
		end
	else
		gameObjects[gobjId] = nil
	end
end
Example call:

Code: Select all

gobj.script.moveObjectDistance(gazingStatue,{dy=-8,vy=-1.6,callbacky=destroyStatue})

Code: Select all

-- WARNING: THIS FUNCTION IS REFERENCED FROM gobj.script. DO NOT USE
-- LOCAL ENVIRONMENT.
function destroyStatue(statue)
  statue:destroy()
end
This moves the object "gazingStatue" 8 meters down at a speed of 1.6 m/s, and at the end, calls destroyStatue with the gazingStatue object as the only argument. Note that callbacks are referenced from the script with the translation code, which means that you should never try to use the local environment, only the function's arguments and global variables (here's why).

edit: updated with bug fix, huge thanks to alois for finding the bug!
Last edited by minmay on Sat Oct 24, 2015 7:28 am, edited 1 time in total.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
User avatar
THOM
Posts: 1281
Joined: Wed Nov 20, 2013 11:35 pm
Location: Germany - Cologne
Contact:

Re: Re-use of animation pushable block?

Post by THOM »

Hey minmay - finally I found the time to have a look at your script. Thanks for that.
I've copied over the update and the moveObjectDistance function, added a timer (to my object). But when I trigger the update-function I get the error "bad argument #1 to 'pairs' (table expected, got nil)."
THOM formaly known as tschrage
_______________________________________________
My MOD (LoG1): Castle Ringfort Thread
My MOD (LoG2): Journey To Justice Thread | Download
minmay
Posts: 2790
Joined: Mon Sep 23, 2013 2:24 am

Re: Re-use of animation pushable block?

Post by minmay »

Because you only copied one function instead of the whole script...if you actually look at the functions, you'll see they call a lot of other functions and use some persistent data. I pasted the whole script because you need the whole script to do object transformation. (Well, you don't need the "SIMPLE STUFF" section, but you need the rest.)
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
User avatar
THOM
Posts: 1281
Joined: Wed Nov 20, 2013 11:35 pm
Location: Germany - Cologne
Contact:

Re: Re-use of animation pushable block?

Post by THOM »

aaah - yes! I hoped, I could save unused stuff. my mod is packed enough... (and I havn't implemented grimTK, yet...)
[and I couldn't find a command "pairs" in the rest of the script - therefore I thought I didn't leave out something important.]

well - now it works all in all, but the command moveObjectDistance(self.go,{dy=-3,vy=-1.6, moves the object down. What do I have to use if I want the object to move horizontally?
THOM formaly known as tschrage
_______________________________________________
My MOD (LoG1): Castle Ringfort Thread
My MOD (LoG2): Journey To Justice Thread | Download
Post Reply