[SCRIPT] Chests - keylocked/picklocking/trapped

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!
Post Reply
User avatar
akroma222
Posts: 1029
Joined: Thu Oct 04, 2012 10:08 am

[SCRIPT] Chests - keylocked/picklocking/trapped

Post by akroma222 »

Hey All,
Firstly, without the help of MrChoke, Prozail and jKos, from this thread viewtopic.php?f=22&t=8405...
I would still be pulling my hair out Im sure, thanks guys for the keylocked chest definition ;)
Also thank you jKos again for the "script_autotrigger"

This thread is for if you want to:
1. Have a locks and traps skill
2. Base the success of picklocking a locked chest on champions class and locks and traps skill
3. Have your chests trapped
4. Have your champions (need class and locks/traps skill) to be able to discover and disarm the trap.

Locks and Traps skill (init.lua or create and import skills.lua)
SpoilerShow

Code: Select all

defineSkill{
	name = "locks_traps",
	uiName = "Locks and Traps",
	priority = 170,
	icon = 32,
	description = "Champion is able to pick locks, spot and disarm traps.",
 },
}
Place these definitions ("chest_keylocked","chest_picklock") into your objects.lua:
SpoilerShow

Code: Select all

defineObject{
	name = "chest_keylocked",
       	baseObject = "chest",
       	components = { 
		{
       			class = "Script",
       			name = "data",
       			source = [[
    			data = {}
    			function get(self,name)
       				return self.data[name]
    			end
    			function set(self,name,value)
       				self.data[name] = value
    			end
    			]],      
    		},
             	{
              		class = "Lock",
                  	name = "keyLock"       
             	},
             	{
                	class = "Clickable",
                	offset = vec(0,0.5,0),
             		size = vec(1.5, 1.0, 1.0),
             		maxDistance = 1,         
             		onClick = function(self)   
                		if not self.go.chest:getLocked() then
                   			if self.go:getComponent("keyLock") then            
                      				self.go:removeComponent("keyLock")
                   			end
					chestScript.script.trapCheck(self)	
                   			return true
                		else
                   			local used_item = getMouseItem()
                   			if not self.go.chest:getLocked() then
						chestScript.script.trapCheck(self)
                      				return false
                   			end
                   			if used_item == nil then
						chestScript.script.trapCheck(self)
                      				return false
                   			end

                   			local stackSize = used_item:getStackSize()
                   			if used_item.go.name == 'lock_pick' then                            
                      				setMouseItem(nil)
                     	 			playSound("lock_incorrect")      
                      				hudPrint("This chest requires a key and cannot be picked.")
                      				delayedCall("chestScript", 0.2, "delayedGiveLockPick", used_item.go.name, stackSize)
                      				chestScript.script.trapCheck(self)	
						return false
                   
					elseif not used_item.go.item:hasTrait("key") then
                      				hudPrint("You must open the chest with a key.")
						chestScript.script.trapCheck(self)	
                      				return false
                   			
					elseif self.go.keyLock:getOpenedBy() == used_item.go.name then
                      				self.go.chest:setLocked(false)
                      				setMouseItem(nil)
                      				self.go.lock:disable()
                      				playSound("key_lock")
                      				self.go:removeComponent("keyLock")

                      				-- If you don't want the key back, remove the line below
                      				--delayedCall("chestScript", 0.2, "delayedGiveLockPick", used_item.go.name)                          
                      				
						chestScript.script.trapCheck(self)	
						return true
                   			else
                      				hudPrint("Not the right key.")
                      				playSound("key_lock_faint")
						chestScript.script.trapCheck(self)	
                      				return false
					end
                   		end   
                	end,
             	}, 
       },
    }


defineObject{
	name = "chest_picklock",
       	baseObject = "chest",
       	components = { 
		{
       			class = "Script",
       			name = "data",
       			source = [[
    			data = {}
    			function get(self,name)
       				return self.data[name]
    			end
    			function set(self,name,value)
       				self.data[name] = value
    			end
    			]],      
    		},
             	{
              		class = "Lock",
                  	name = "keyLock"       
             	},
             	{
                	class = "Clickable",
                	offset = vec(0,0.5,0),
             		size = vec(1.5, 1.0, 1.0),
             		maxDistance = 1,         
             		onClick = function(self)      
                		if not self.go.chest:getLocked() then
                   			if self.go:getComponent("keyLock") then            
                      				self.go:removeComponent("keyLock")
                   			end
					chestScript.script.trapCheck(self)
                   			return true
                		else
                   			local used_item = getMouseItem()
                   			if not self.go.chest:getLocked() then
						chestScript.script.trapCheck(self)
                      				return false
                   			end
                   			if used_item == nil then
						chestScript.script.trapCheck(self)
                      				return false
                   			end

                   			local stackSize = used_item:getStackSize()
                   			if used_item.go.name == 'lock_pick' then
						for i = 1,4 do
		
							local c = party.party:getChampion(i)
		
							local cLocks = c:getSkillLevel("locks_traps")
		
							local cDex = c:getBaseStat("dexterity")
		
							local cClass = c:getClass()
		
		
							
							if cLocks ~= nil then

								if cClass == "rogue" then
								--or cClass == "ninja" 
								--or cClass == "mechanic"
								--or cClass == "tomb_raider"
								--or cClass == "pirate"                            --change to reflect which class is able to discover traps
									
									if math.random(1,100) <= (cDex * cLocks) then
						
										hudPrint(""..c:getName().." picked the lock!")
	
										self.go.chest:setLocked(false)
                      								setMouseItem(nil)
                      								self.go.lock:disable()
                      								playSound("key_lock")
                      								self.go:removeComponent("keyLock")
										chestScript.script.trapCheck(self)
										local lockpick = spawn("lock_pick",party.level,party.x,party.y,party.elevation)
    
										if stackSize == 1 then
    	
											return true
										else
											lockpick.item:setStackSize(stackSize - 1) 
											setMouseItem(lockpick.item)  
 
											return true	   
										end
 
									else	
										setMouseItem(nil)
                     	 							playSound("lock_incorrect")      
                      								hudPrint(""..c:getName().." failed to pick the lock...")

                      								delayedCall("chestScript", 0.2, "delayedGiveLockPick", used_item.go.name, stackSize)
										chestScript.script.trapCheck(self)
                      								return false 
				
									end

								else
						
									setMouseItem(nil)
                     	 						playSound("lock_incorrect")      
                      							hudPrint(""..c:getName().."'s class can not use lockpicks...")

									delayedCall("chestScript", 0.2, "delayedGiveLockPick", used_item.go.name, stackSize)
                      							chestScript.script.trapCheck(self)
									return false 
					
								end
				
							else
						
								setMouseItem(nil)
                     	 					playSound("lock_incorrect")      
                      						hudPrint("No one has Locks and Traps skill above 0...")

                      						delayedCall("chestScript", 0.2, "delayedGiveLockPick", used_item.go.name, stackSize)
                      						chestScript.script.trapCheck(self)
								return false 
					
							end
		
						end
      
                   
					elseif used_item.go.item:hasTrait("key") then
                      				hudPrint("This chest can only be picklocked...")
						chestScript.script.trapCheck(self)
                      				return false
                   			else
                      				hudPrint("This chest can only be picklocked...")
                      				playSound("key_lock_faint")
						chestScript.script.trapCheck(self)
                      				return false
					end   
                		end
                	end,
             	}, 
       },
    }

Place this "toolkit" definition into your items.lua:
SpoilerShow

Code: Select all

defineObject{
	name = "toolkit",
	baseObject = "base_item",
	components = {
		{
			class = "Model",
			model = "assets/models/items/lockpicks.fbx",	
		},
		{
			class = "Item",
			uiName = "Tool Kit",
			description = "Tools for disarming traps.",
			gfxIndex = 356,
			impactSound = "impact_blade",
			weight = 3,
			primaryAction = "disarm",
			traits = { "usable", "two_handed" },
		},
		{
			class = "ItemAction",
			name = "disarm",
			uiName = "Disarm",
			cooldown = 4,
			onAttack = function(self, champion, slot)
				local dx,dy = getForward(party.facing)
				local cClass = champion:getClass()

				if cClass == "rogue" then  
				--or cClass == "ninja"
				--or cClass == "mechanic"
				--or cClass == "tomb_raider"
				--or cClass == "pirate"                 --change to reflect which class is able to disarm traps

					for e in party.map:entitiesAt(party.x + dx, party.y + dy) do
						if e.chest and e.data then
							if e.data:get("trapped") then
								return chestScript.script.disarmTrap(self, champion, slot, e)
							else
								hudPrint("The chest is not trapped...")
								return false
							end
						else	
							hudPrint("There is no trap here.")
							return false
						end
					end
				else
					hudPrint(""..champion:getName().." can not use the toolkit.")
					return false
				end
				
			end,
		},
	},
}
Place jKos's "script_autotrigger" definition into init.lua
SpoilerShow

Code: Select all

defineObject{
       name = 'script_autotrigger',
       baseObject = 'script_entity',
        components = {
          {
             class = 'FloorTrigger'
          },
          {
             class = "Script",
             source = [[
    		function execute(trigger)
       			-- your code here
    		end
    		self.go.floortrigger:addConnector('onActivate',self.go.id,'execute')
             ]]
          },
        }   
    }
Also use these party hooks in init.lua
SpoilerShow

Code: Select all

defineObject{
       name = "party",
       baseObject = "party",
       components = {
        {
        class = "Party",
         onMove = function(party,dir)
	     	--print(party.go.id,"moving to",dir)
		local t = findEntity("toolkitTimer")
		if t ~= nil then
			return chestScript.script.disarmStop()
		end
	end,
        onTurn = function(party,dir,arg1)
             	--print(party.go.id,"turning to",dir)
		local t = findEntity("toolkitTimer")
		if t ~= nil then
			return chestScript.script.disarmStop()
		end
        end,
onDie = function(party,champion)
		local t = findEntity("toolkitTimer")
		if t ~= nil then
			return chestScript.script.disarmStop()
		end
        end,
onRest = function(party)
             	--print(party.go.id,'is resting')
		local t = findEntity("toolkitTimer")
		if t ~= nil then
			return chestScript.script.disarmStop()
		end
        end,
}
       },
    }
Place a script entity called (chestScript) in your dungeon with this pasted into it:
SpoilerShow

Code: Select all

----------------------------------------------------------checkChampion()

function checkChampion()
	for i = 1,4 do
		local c = party.party:getChampion(i)
		local cLocks = c:getSkillLevel("locks_traps")
		local cClass = c:getClass()
		
		if cClass == "rogue"  then
		--or cClass == "ninja" 
		--or cClass == "mechanic"
		--or cClass == "tomb_raider"
		--or cClass == "pirate"                    --change to reflect which class is able to discover traps
			
			if cLocks > 0 then
				return true
			end
		end
		return false
	end
end

--------------------------------------------------------------------------------get lock picks back!
function delayedGiveLockPick(itemName, stackSize)
	local lockpick = spawn(itemName,party.level,party.x,party.y,party.elevation)
    if stackSize then
    	lockpick.item:setStackSize(stackSize)   
    end
    setMouseItem(lockpick.item)
end
-------------------------------------------------------------------------------trapCheck(self)
function trapCheck(self)			
	if self.go.data:get("trapped") then
		spawner_1.spawner:activate()                                       -- enter your own trap script here
		hudPrint("trap goes off...")
		if math.random(1,10) <= self.go.data:get("recurring") then
			hudPrint("...and is still armed!")
		else
			self.go.data:set("recurring",0)
			self.go.data:set("trapped",false)   
			hudPrint("...and has not re-armed.")
		end
	end
end
---------------------------------------------------------------------------------------disarming scripts
interruptedDisarm = false
-------------------------------------------------------disarmTrap(self, champion, slot)
function disarmTrap(self, champion, slot)

	local t = findEntity("toolkitTimer")
	if t ~= nil then
		t:destroy()
	end
	interruptedDisarm = false
	GameMode.fadeOut(0, 2) 
	playSound("lock_incorrect")
	hudPrint(""..champion:getName().." is attempting to disarm the trap...")
	
	spawn("timer",party.level,party.x,party.y,party.elevation,party.facing,"toolkitTimer")
	toolkitTimer.timer:setTimerInterval(4)
	toolkitTimer.timer:start()
	
	delayedCall("chestScript", 4, "disarmResult", champion, champion:getSkillLevel("locks_traps"))
end
------------------------------------------------------disarmResult(champion, skill)
function disarmResult(champion, skill)

	local t = findEntity("toolkitTimer")
	if t ~= nil then
		t:destroy()
	end
	if interruptedDisarm == true then
		return false
	end
	GameMode.fadeIn(1, 0.1)
	 
	if math.random(1,10) <= skill then
		local dx,dy = getForward(party.facing)
		for e in party.map:entitiesAt(party.x + dx, party.y + dy) do
			if e.chest and e.data then
				e.data:set("trapped",false)    
	    		e.data:set("recurring",0)
			end
		end
		playSound("key_lock")
		hudPrint(""..champion:getName().." disarmed the trap!")
	else
		for s in party.map:entitiesAt(party.x, party.y) do
			if s.script and s.floortrigger then
				s.script:trap(self)
			end
		end
		playSound("lock_incorrect")
		hudPrint(""..champion:getName().." failed to disarm the trap.")
	end
end
------------------------------------------------------------disarmStop()
function disarmStop()
	local t = findEntity("toolkitTimer")
	if t ~= nil then
		t:destroy()
	end
	interruptedDisarm = true
	GameMode.fadeIn(1, 0.1)
	hudPrint("Disarming interrupted...")
end
-------------------------------------------------------------------------------------------------------
TO SET UP A TRAPPED KEYLOCK CHEST

1. Place a chest_keylocked into your dungeon
2. tick the locked and keylocked checkboxes, also enter in the key you want the chest to be openned by
2. Place a script_autotrigger in front of the chest with this script in it
SpoilerShow

Code: Select all

function execute(trigger)
       			if chest_keylocked_1.data:get("trapped") == false then            
					if chestScript.script.checkChampion() == true then
						hudPrint("This chest is not trapped...")         
					end
				else
       				chest_keylocked_1.data:set("trapped",true)    
    				chest_keylocked_1.data:set("recurring",5) --change the recurring to alter whether the trap will be re-armed when you set it off (1 rarely, 10 always)
					if chestScript.script.checkChampion() == true then
						hudPrint("This chest is trapped!")
					end
				end
    		end
    		self.go.floortrigger:addConnector('onActivate',self.go.id,'execute')
             
               function trap(trigger)
			spawner_1.spawner:activate()       -- enter your own trap script here
		end
3. Place a spawner (spawner_1) aimed at the party (or not) to spawn a fireball as the trap (change to whatever trap you like.... spikes, poison cloud, trapdoor etc)
4. change the chest_keylocked_1.data:set("recurring",5) line to alter whether the trap will be re-armed when you set it off (1 rarely re-arms, 10 always rearms)
5. Currently only Rogues will be able to do all of the above.... --change to reflect which class is able to discover traps

When party steps onto the script_autotrigger, chestScript.script.checkChampion() will check to see if a champ is off the right class and whether they have Locks and Traps skill > 0
If they do a hudPrint msg should inform you of whether the chest is trapped or not (not re-armed). If you dont then no msg is given (they do not spot the trap)
Then onClick will set the trap off.... based on the recurring value, the trap will either re-arm or not.
You will then need the correct key to open the chest.
Every time you try to open the chest with any key the trap script of the script_autotrigger will go off (if the trap is still armed)

---------------------------------------------------------------------------------------------
TO SET UP A TRAPPED PICKLOCK CHEST

1. Place a chest_picklock into your dungeon
2. tick the locked and keylocked checkboxes
2. Place a script_autotrigger in front of the chest with this script in it
SpoilerShow

Code: Select all

function execute(trigger)
       			if chest_picklock_1.data:get("trapped") == false then            
					if chestScript.script.checkChampion() == true then
						hudPrint("This chest is not trapped...")         
					end
				else
       				chest_picklock_1.data:set("trapped",true)    
    				chest_picklock_1.data:set("recurring",5)                              --change the recurring to alter whether the trap will be re-armed when you set it off (1 rarely rearm, 10 always rearmed)
					if chestScript.script.checkChampion() == true then
						hudPrint("This chest is trapped!")
					end
				end
    		end
    		self.go.floortrigger:addConnector('onActivate',self.go.id,'execute')
             
               function trap(trigger)
			spawner_1.spawner:activate()       -- enter your own trap script here
		end
3. Place a spawner (spawner_1) aimed at the party (or not) to spawn a fireball as the trap (change to whatever trap you like.... spikes, poison cloud, trapdoor etc)
4. change the chest_picklock_1.data:set("recurring",5) line to alter whether the trap will be re-armed when you set it off (1 rarely re-arms, 10 always rearms)
5. Currently only Rogues will be able to do all of the above.... --change to reflect which class is able to discover traps

When party steps onto the script_autotrigger, chestScript.script.checkChampion() will check to see if a champ is off the right class and whether they have Locks and Traps skill > 0
If they do a hudPrint msg should inform you of whether the chest is trapped or not (not re-armed). If you dont then no msg is given (they do not spot the trap)
Then onClick will set the trap off.... based on the recurring value, the trap will either re-arm or not.
You will then need a picklock to open the chest.
When you attempt to pick the chest lock champions Locks and Traps skill x champion Dexterity is used (if math.random(1,100) <= (cDex * cLocks) then)
If they fail, you do not lose the lockpick... and every time you try to pick the lock the trap script of the script_autotrigger will go off (if the trap is still armed)

---------------------------------------------------------------------------------------------
DISARMING THE TRAP

All you need to do is stand in front of the chest and "attack" with the Tool Kit in hand.
The chance of disarming is simply (if math.random(1,10) <= locks and traps skill then)
It will fade in and out like you are digging and you can interrupt the disarming process by either moving, turning, resting or dying (you may want to add attacking or being damaged to that list??)
If you fail the trap will go off, if you succeed then the trap is removed (chest_picklock_1.data:set("trapped",true) )

---------------------------------------------------------------------------------------------
Room for improvement-
1. Not sure how to enforce a cooldown on all champs like the digging fade out does... nor have I used any GUI digging... etc
2. Tool Kit just looks like a picklock for now
3. Only key locked and key incorrect sounds are used... I will find some other sounds soon
4. An appropraite icon for the Locks and Traps skill
5. I am not sure how to work it so that the champion with the highest Locks and Traps skill is used... now it just cycles through and uses the first champ who has the right class and Locks and Traps skill > 0
I am sure it is an easy fix but I have screwed around with it for a while to no avail.... help maybe??

Let me know how you go, or if there is anything I have missed

Akroma :D
User avatar
Mal85
Posts: 103
Joined: Fri Nov 16, 2012 10:56 am

Re: [SCRIPT] Chests - keylocked/picklocking/trapped

Post by Mal85 »

Very cool, I'll have to implement these in my dungeon. Thanks for putting it all together in an easy to read format. I had been following the other threads for a bit but it was kinda messy and I like the idea of a disarm trap skill. Seems like a no brainer for a rogue to have in a dungeon crawler ^^
Post Reply