Need help with item-based combination lock [SOLVED!]

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
BinaryCat
Posts: 3
Joined: Mon Jun 13, 2016 3:26 am

Need help with item-based combination lock [SOLVED!]

Post by BinaryCat »

Hey! Before I start, I want to say that I have searched the megathread and other sources for answers, but I've been coming up short. Though what I have learned has been a great help generally.

I'm working on a combination lock puzzle. The gist of it is this: the player has six "punch cards" (defined "punchcard" as a new item), ID'd from 1 to 6 (punchcard_1, etc). The goal is for the door to unlock if the player places the proper punch cards on the surface. (Putting something that isn't a punchcard on the surface also gives the player an error message and tells them to restart the puzzle.) I plan on having several puzzles related to these cards, so they're meant to be used repeatedly in other puzzles.

The kicker is that I want the order to matter - card 4, then 2, then 3, or whatever. There's a counter which only increments when the correct cards are placed in the correct order.

So, while the system recognizes the first card placed, and recognizes when all six are placed (which generates an "incorrect passcode" message for the player, since the code is only three numbers long), and even recognizes when a non-punchcard item is placed, it doesn't recognize any subsequent cards placed - it keeps detecting the first one placed and only that one. Because of that, it keeps resetting the counter to 0 so it loses track of the order the cards were placed in, making success impossible. The script is attached to a "castle_wall_tray" asset, if that matters.

Code: Select all

function identifyPunchcard(surface)

-- determine which punch card was placed on the reader, and returns 0 if the item wasn't a punchcard (currently always reads the first item placed, but I need it to read the most recent item placed)

	for v,i in surface:contents() do
	if i.go.id == "punchcard_1" then return 1
	elseif i.go.id == "punchcard_2" then return 2
	elseif i.go.id == "punchcard_3" then return 3
	elseif i.go.id == "punchcard_4" then return 4
	elseif i.go.id == "punchcard_5" then return 5
	elseif i.go.id == "punchcard_6" then return 6
	else return 0
	end
	end
	
end


function tallyPunchcards(surface)

-- a secondary check, to make sure only those cards that are part of the code are on the reader

	local punchCombo = 0

	for v,i in surface:contents() do
	if i.go.id == "punchcard_1" then punchCombo = punchCombo + 1 end
	if i.go.id == "punchcard_2" then punchCombo = punchCombo + 2 end
	if i.go.id == "punchcard_3" then punchCombo = punchCombo + 4 end
	if i.go.id == "punchcard_4" then punchCombo = punchCombo + 8 end
	if i.go.id == "punchcard_5" then punchCombo = punchCombo + 16 end
	if i.go.id == "punchcard_6" then punchCombo = punchCombo + 32 end
	if i.go.name ~= "punchcard" then return 0 end
	end
	
	return punchCombo
	
end


function checkPasscode()

--main function checks each card as it is placed and increments the counter if the card is the correct one in the sequence, then checks to see if the unlock conditions are met

	if identifyPunchcard(passcode_tray_storehouse.surface) == 1
	then storehouse_password_count.counter:setValue(0)
	hudPrint("Entered 1")
	
	elseif identifyPunchcard(passcode_tray_storehouse.surface) == 2
	then hudPrint("Entered 2")
		if storehouse_password_count.counter:getValue() == 1
		then storehouse_password_count.counter:increment()
		else storehouse_password_count.counter:setValue(0)
		end

	elseif identifyPunchcard(passcode_tray_storehouse.surface) == 3
	then hudPrint("Entered 3")
	storehouse_password_count.counter:setValue(0)
		if storehouse_password_count.counter:getValue() == 2
		then storehouse_password_count.counter:increment()
		else storehouse_password_count.counter:setValue(0)
		end

	elseif identifyPunchcard(passcode_tray_storehouse.surface) == 4
	then hudPrint("Entered 4")
		if storehouse_password_count.counter:getValue() == 0 
		then storehouse_password_count.counter:increment()
		else storehouse_password_count.counter:setValue(0)
		end
	
	elseif identifyPunchcard(passcode_tray_storehouse.surface) == 5
	then hudPrint("Entered 5")
	storehouse_password_count.counter:setValue(0)
	
	elseif identifyPunchcard(passcode_tray_storehouse.surface, "punchcard_6") == 6
	then hudPrint("Entered 6")
	storehouse_password_count.counter:setValue(0)
	
	else storehouse_password_count.counter:setValue(0)
	end

	if storehouse_password_count.counter:getValue() == 3 and tallyPunchcards(passcode_tray_storehouse.surface) == 14
	then hudPrint("Passcode accepted!")
	storehouse_door.door:open()
	elseif tallyPunchcards(passcode_tray_storehouse.surface) == 63
	then party.party:shakeCamera(0.02, 1)
	hudPrint("Passcode denied!")
	elseif tallyPunchcards(passcode_tray_storehouse.surface) == 0
	then hudPrint("Error! Non-punchcard item detected. Please remove all items and try again.")
	end
	
end
If it wasn't for the order requirement, this would be an easy fix, but I haven't been able to find a way to get the script to read the full stack - it always just reads the first one. Sorry if the code's a mess, this is my first foray into LUA and it's been a long while since I worked with anything else.
Last edited by BinaryCat on Mon Jun 13, 2016 4:06 pm, edited 1 time in total.
minmay
Posts: 2790
Joined: Mon Sep 23, 2013 2:24 am

Re: Need help with item-based combination lock

Post by minmay »

If you want a puzzle to depend on the order in which items are added to a surface, you need to use the surface's onInsertItem event. I assume you have a connector from the surface's onInsertItem event to checkPasscode()? If so, then if you change the signature to checkPasscode(surface, item) you can see the item that was just inserted. That makes it easy to determine the order the items were inserted in.
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.
BinaryCat
Posts: 3
Joined: Mon Jun 13, 2016 3:26 am

Re: Need help with item-based combination lock

Post by BinaryCat »

Thanks. :) Yup, checkPasscode is connected to the surface from the onInsertItem event.

I hate to admit it, but after even more reading and fiddling, I'm still stuck. I changed checkPasscode() to "checkPasscode(surface, item)" as you suggested. I haven't been able to actually use that information though. All my attempts to try and refer to the most recent item placed have given me nil errors whenever I try placing an object on the surface, so I'm definitely not using it correctly. Would you be willing to elaborate a bit?

I'd love to blame being tired, but I'm just rather new to this. It was fairly simple when I tried building the puzzle without caring about the order, or using multiple surfaces/alcoves. But this is really tripping me up.
minmay
Posts: 2790
Joined: Mon Sep 23, 2013 2:24 am

Re: Need help with item-based combination lock

Post by minmay »

Every hook on a Component can have connectors attached to it. A connector consists of an event, a target, and an action. The event is the name of the hook itself, which in this case is "onInsertItem". The target is the GameObject to send a message to when that hook is triggered. In this case, the target is your script entity. The target must have a component named "controller".
The action means one of two things. If the component named "controller" is a ControllerComponent, then it's the controller function that gets called. If the action is "start", the ControllerComponent's start() function will be called. So if the target is "counter_1" and the action is "decrement", triggering the connector by inserting an item is equivalent to:

Code: Select all

counter_1.controller:decrement()
However, if the component named "controller" is a ScriptControllerComponent - which is what the script_entity object has - then the action is the name of a function in the ScriptComponent that gets called. In this case, all the arguments received by the triggering Component's hook are passed on to the ScriptComponent function. If you look at the scripting reference entry for SurfaceComponent, you'll see that its hook gets two arguments:

Code: Select all

SurfaceComponent.onInsertItem(self, item)
self is the SurfaceComponent the item was inserted into, and item is the ItemComponent that was just inserted into it.
So let's say you have an object named "dungeon_alcove_1" with a SurfaceComponent named "surface". That SurfaceComponent has a connector with the event "onInsertItem", the target is a script_entity named "script_1", and the action is "checkPasscode". Then, if you insert a scroll with the id "scroll_1", the connector will execute equivalently to:

Code: Select all

script_1.script.checkPasscode(dungeon_alcove_1.surface,scroll_1.item)
(Note that this is an unbound call, the ScriptComponent does not receive itself as the first argument, it receives the SurfaceComponent as the first argument.)
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.
BinaryCat
Posts: 3
Joined: Mon Jun 13, 2016 3:26 am

Re: Need help with item-based combination lock

Post by BinaryCat »

That makes so much sense now that I've had it explained to me a bit. I was able to delete the identifyPunchcard function entirely and got the puzzle working exactly as intended.

Thanks a ton! :D


Here's the final code, if you're curious.

Code: Select all

function tallyPunchcards(surface)

-- a secondary check, to make sure only those cards that are part of the code are on the reader, returns 0 if there are any non-punchcard items

	local punchCombo = 0

	for v,i in surface:contents() do
	if i.go.id == "punchcard_1" then punchCombo = punchCombo + 1 end
	if i.go.id == "punchcard_2" then punchCombo = punchCombo + 2 end
	if i.go.id == "punchcard_3" then punchCombo = punchCombo + 4 end
	if i.go.id == "punchcard_4" then punchCombo = punchCombo + 8 end
	if i.go.id == "punchcard_5" then punchCombo = punchCombo + 16 end
	if i.go.id == "punchcard_6" then punchCombo = punchCombo + 32 end
	if i.go.name ~= "punchcard" then return 0 end
	end
	
	return punchCombo
	
end


function checkPasscode(surface, item)

--main function checks each card as it is placed and increments the counter if the card is the correct one in the sequence, then checks to see if the unlock conditions are met

	if item.go.id == "punchcard_1"
	then storehouse_password_count.counter:setValue(0)
	hudPrint("Entered 1")
	--hudPrint(tostring(storehouse_password_count.counter:getValue()))
	
	elseif item.go.id == "punchcard_2"
	then hudPrint("Entered 2")
	--hudPrint(tostring(storehouse_password_count.counter:getValue()))
		if storehouse_password_count.counter:getValue() == 1
		then storehouse_password_count.counter:increment()
		--hudPrint(tostring(storehouse_password_count.counter:getValue()))
		else storehouse_password_count.counter:setValue(0)
		end

	elseif item.go.id == "punchcard_3"
	then hudPrint("Entered 3")
	--hudPrint(tostring(storehouse_password_count.counter:getValue()))
		if storehouse_password_count.counter:getValue() == 2
		then storehouse_password_count.counter:increment()
		--hudPrint(tostring(storehouse_password_count.counter:getValue()))
		else storehouse_password_count.counter:setValue(0)
		end

	elseif item.go.id == "punchcard_4"
	then hudPrint("Entered 4")
	--hudPrint(tostring(storehouse_password_count.counter:getValue()))
		if storehouse_password_count.counter:getValue() == 0 
		then storehouse_password_count.counter:increment()
		--hudPrint(tostring(storehouse_password_count.counter:getValue()))
		else storehouse_password_count.counter:setValue(0)
		end
	
	elseif item.go.id == "punchcard_5"
	then hudPrint("Entered 5")
	storehouse_password_count.counter:setValue(0)
	--hudPrint(tostring(storehouse_password_count.counter:getValue()))
	
	elseif item.go.id == "punchcard_6"
	then hudPrint("Entered 6")
	storehouse_password_count.counter:setValue(0)
	--hudPrint(tostring(storehouse_password_count.counter:getValue()))
	
	else storehouse_password_count.counter:setValue(0)
	--hudPrint(tostring(storehouse_password_count.counter:getValue()))
	end

	if storehouse_password_count.counter:getValue() == 3 and tallyPunchcards(passcode_tray_storehouse.surface) == 14
	then hudPrint("Passcode accepted!")
	storehouse_door.door:open()
	passcode_tray_storehouse.clickable:disable()
	--hudPrint(tostring(storehouse_password_count.counter:getValue()))
	elseif tallyPunchcards(passcode_tray_storehouse.surface) == 63
	then party.party:shakeCamera(0.02, 1)
	hudPrint("Passcode denied!")
	elseif tallyPunchcards(passcode_tray_storehouse.surface) == 0
	then hudPrint("Error! Non-punchcard item detected. Please remove all items and try again.")
	end
	
end
Post Reply