Keyring + Smart Locks

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
Zo Kath Ra
Posts: 944
Joined: Sat Apr 21, 2012 9:57 am
Location: Germany

Keyring + Smart Locks

Post by Zo Kath Ra »

The keyring works just like a sack/wooden_box, but only accepts items with the "key" trait.
It also remembers the inventory position where it was last stored.

The keyring only works in combination with smart locks.

Pick up the key ring and click it on a smart lock => the lock searches for a matching key inside the keyring.
If the lock finds a matching key, then the key becomes the mouse cursor, and the keyring (minus the matching key) is returned to its last position in a champion's inventory.

Add this code to your mod's "items.lua":

Code: Select all

defineObject{
    name = "keyring",
    baseObject = "base_item",
    components = {
        {
            class = "Model",
            model = "assets/models/items/lockpicks.fbx",
        },
        {
            class = "Item",
            uiName = "Keyring",
            gfxIndex = 356,
            gfxIndexContainer = 356,
            weight = 0.2,
            
            onEquipItem = function(self, champion, slot)
                self.go.data:set("champion", champion:getOrdinal())
                self.go.data:set("slot", slot)
            end,
        },
        {
            class = "ContainerItem",
            containerType = "chest",
            openSound = "container_box_open",
            closeSound = "container_box_close",
            
            onInsertItem = function(self, item, slot)
                if (item:hasTrait("key")) then
                    -- accept keys
                else
                    hudPrint("The keyring can only store keys!")
                    
                    local altar_local = spawn("altar", party.level, party.x, party.y, party.facing, party.elevation)
                    altar_local.surface:addItem(item)
                    
                    self:removeItemFromSlot(slot)
                    
                    delayedCall("script_entity_give_mouse_item", 0, "giveMouseItem", item.go.id, altar_local.id)
                    
                    if (getMouseItem() ~= nil) then
                        if (getMouseItem().go.id ~= item.go.id) then
                            self:insertItem(slot, getMouseItem())
                        end
                    end
                end
            end,
            
            onRemoveItem = function(self, item)
                -- todo
            end,
        },
        {
            class = "Script",
            name = "data",
            source = [[
                data = { ["champion"] = 0, ["slot"] = 0 }
                
                get = function(self, name)
                    return self.data[name]
                end
                
                set = function(self, name, value)
                    self.data[name] = value
                end
            ]],
        },
    }
}
This to "objects.lua":

Code: Select all

defineObject{
    name = "lock_gold_smart",
    baseObject = "lock",
    components = {
        {
            class = "Model",
            model = "assets/models/env/wall_lock_golden.fbx",
            offset = vec(0, 1.375, 0),
            staticShadow = true,
        },
        
        {
            class = "Clickable",
            offset = vec(0, 1.375, 0),
            size = vec(0.4, 0.4, 0.4),
            
            onClick = function(self)
                local mouse_item = getMouseItem()
                
                if (mouse_item ~= nil) and (mouse_item.go.name == "keyring") then
                    -- go through all items in the conainer
                    local key_slot = 0
                    local slot_item
                    
                    for i = 1, mouse_item.go.containeritem:getCapacity() do
                        slot_item = mouse_item.go.containeritem:getItem(i)
                        
                        if (slot_item ~= nil) and (slot_item.go.item:hasTrait("key")) and (self.go.lock:getOpenedBy() == slot_item.go.name) then
                            key_slot = i
                            break
                        end
                    end
                    
                    local data_champion = mouse_item.go.data:get("champion")
                    local data_slot = mouse_item.go.data:get("slot")
                    
                    if (key_slot > 0) then
                        -- a matching key was found
                        if (data_champion == 0) or (data_slot == 0) then
                            -- inventory position not set
                            print("Can't return keyring to inventory - not set")
                        elseif (party.party:getChampionByOrdinal(data_champion):getItem(data_slot) ~= nil) then
                            -- inventory position set, but occupied
                            print("Can't return keyring to inventory - occupied")
                        else
                            -- inventory position set and empty
                            local altar_0 = spawn("altar", party.level, party.x, party.y, party.facing, party.elevation)
                            altar_0.surface:addItem(slot_item)
                            mouse_item.go.containeritem:removeItemFromSlot(key_slot)
                            
                            party.party:getChampionByOrdinal(data_champion):insertItem(mouse_item.go.data:get("slot"), mouse_item)
                            
                            delayedCall("script_entity_give_mouse_item", 0, "giveMouseItem", slot_item.go.id, altar_0.id)
                        end
                    else
                        -- no matching key was found
                        if (data_champion == 0) or (data_slot == 0) then
                            -- inventory position not set
                            print("Can't return keyring to inventory - not set")
                        elseif (party.party:getChampionByOrdinal(data_champion):getItem(data_slot) ~= nil) then
                            -- inventory position set, but occupied
                            print("Can't return keyring to inventory - occupied")
                        else
                            -- inventory position set and empty
                            party.party:getChampionByOrdinal(data_champion):insertItem(mouse_item.go.data:get("slot"), mouse_item)
                            setMouseItem(nil)
                        end
                    end
                end
            end
        },
    },
}
And this to a script entity named "script_entity_give_mouse_item":

Code: Select all

function giveMouseItem(item_id, altar_id)
	local item_local = findEntity(item_id)
	local altar_local = findEntity(altar_id)
	
	if ((item_local ~= nil) and (altar_local ~= nil)) then
		setMouseItem(item_local.item)
		altar_local:destroy()
	end
end
User avatar
cromcrom
Posts: 549
Joined: Tue Sep 11, 2012 7:16 am
Location: Chateauroux in a socialist s#!$*&% formerly known as "France"

Re: Keyring + Smart Locks

Post by cromcrom »

Thanks a lot for sharing :-)
A trip of a thousand leagues starts with a step.
User avatar
Zo Kath Ra
Posts: 944
Joined: Sat Apr 21, 2012 9:57 am
Location: Germany

Re: Keyring + Smart Locks

Post by Zo Kath Ra »

Improved version of the smart locks:

Code: Select all

defineObject{
    name = "lock_gold_smart2",
    baseObject = "lock",
    components = {
        {
            class = "Model",
            model = "assets/models/env/wall_lock_golden.fbx",
            offset = vec(0, 1.375, 0),
            staticShadow = true,
        },
        
        {
            class = "Clickable",
            offset = vec(0, 1.375, 0),
            size = vec(0.4, 0.4, 0.4),
            
            onClick = function(self)
                local item_mouse = getMouseItem()
                local item_keyring
                local item_key
                
                if (item_mouse == nil) then
                    -- go through all champions
                    local champion
                    for i = 1, 4 do
                        champion = party.party:getChampionByOrdinal(i)
                        if (champion ~= nil) then
                            -- go through the champion's inventory
                            for j = 1, 32 do
                                item_keyring = champion:getItem(j)
                                if (item_keyring ~= nil) and (item_keyring.go.name == "keyring") then
                                    -- go through all items in the keyring
                                    for k = 1, item_keyring.go.containeritem:getCapacity() do
                                        item_key = item_keyring.go.containeritem:getItem(k)
                                        
                                        if (item_key ~= nil) and (item_key.go.item:hasTrait("key")) and (self.go.lock:getOpenedBy() == item_key.go.name) then
                                            -- spawn an altar, put the key on it, remove key from keyring
                                            local altar_0 = spawn("altar", party.level, party.x, party.y, party.facing, party.elevation)
                                            altar_0.surface:addItem(item_key)
                                            item_keyring.go.containeritem:removeItemFromSlot(k)
                                            
                                            -- put key in mouse cursor, destroy altar
                                            delayedCall("script_entity_give_mouse_item", 0, "giveMouseItem", item_key.go.id, altar_0.id)
                                            
                                            return
                                        end
                                    end
                                end
                            end
                        end
                    end
                elseif (item_mouse.go.name == "keyring") then
                    local data_champion = item_mouse.go.data:get("champion")
                    local data_slot = item_mouse.go.data:get("slot")
                    
                    -- go through all items in the keyring
                    for i = 1, item_mouse.go.containeritem:getCapacity() do
                        item_key = item_mouse.go.containeritem:getItem(i)
                        
                        if (item_key ~= nil) and (item_key.go.item:hasTrait("key")) and (self.go.lock:getOpenedBy() == item_key.go.name) then
                            -- a matching key was found
                            if (data_champion == 0) or (data_slot == 0) then
                                -- inventory position not set
                                print("Can't return keyring to inventory - not set")
                            elseif (party.party:getChampionByOrdinal(data_champion):getItem(data_slot) ~= nil) then
                                -- inventory position set, but occupied
                                print("Can't return keyring to inventory - occupied")
                            else
                                -- inventory position set and empty
                                -- spawn an altar, put the key on it, remove key from keyring
                                local altar_0 = spawn("altar", party.level, party.x, party.y, party.facing, party.elevation)
                                altar_0.surface:addItem(item_key)
                                item_mouse.go.containeritem:removeItemFromSlot(i)
                                
                                -- return keyring to inventory
                                party.party:getChampionByOrdinal(data_champion):insertItem(item_mouse.go.data:get("slot"), item_mouse)
                                setMouseItem(nil)
                                
                                -- put key in mouse cursor, destroy altar
                                delayedCall("script_entity_give_mouse_item", 0, "giveMouseItem", item_key.go.id, altar_0.id)
                            end
                            
                            return
                        end
                    end
                    
                    -- no matching key was found
                    if (data_champion == 0) or (data_slot == 0) then
                        -- inventory position not set
                        print("Can't return keyring to inventory - not set")
                    elseif (party.party:getChampionByOrdinal(data_champion):getItem(data_slot) ~= nil) then
                        -- inventory position set, but occupied
                        print("Can't return keyring to inventory - occupied")
                    else
                        -- inventory position set and empty
                        -- return keyring to inventory
                        party.party:getChampionByOrdinal(data_champion):insertItem(item_mouse.go.data:get("slot"), item_mouse)
                        setMouseItem(nil)
                    end
                end
            end
        },
    },
}
The new smart lock has the same features as the old one, with this addition:
If the mouse cursor is empty and you click on a smart lock, the lock searches all champions' inventories for a keyring. If it finds a keyring, the lock searches the keyring for a matching key. If it finds one, the key becomes the mouse cursor and is removed from the keyring.
User avatar
Zo Kath Ra
Posts: 944
Joined: Sat Apr 21, 2012 9:57 am
Location: Germany

Re: Keyring + Smart Locks

Post by Zo Kath Ra »

Why require a keyring at all?

Smart lock version 3:

Code: Select all

defineObject{
    name = "lock_gold_smart3",
    baseObject = "lock",
    components = {
        {
            class = "Model",
            model = "assets/models/env/wall_lock_golden.fbx",
            offset = vec(0, 1.375, 0),
            staticShadow = true,
        },
        
        {
            class = "Clickable",
            offset = vec(0, 1.375, 0),
            size = vec(0.4, 0.4, 0.4),
            
            onClick = function(self)
                delayedCall("script_entity_give_mouse_item", 0, "giveMouseItem2", self.go.id)
            end
        },
    },
}
And these two functions in a script entity called "script_entity_give_mouse_item":

Code: Select all

function findKeyInContainer(item_container, id_lock)
	local entity_lock = findEntity(id_lock)
	
	for i = 1, item_container.go.containeritem:getCapacity() do
		local item_slot = item_container.go.containeritem:getItem(i)
		
		if (item_slot) then
			if (item_slot:hasTrait("key")) and (entity_lock.lock:getOpenedBy() == item_slot.go.name) then
				-- matching key found
				setMouseItem(item_slot)
				item_container.go.containeritem:removeItemFromSlot(i)
				return true
			elseif (item_slot.go.containeritem) then
				-- the item is a container
				return(findKeyInContainer(item_slot, id_lock))
			end
		end
	end
	
	-- key not found
	return false
end

function giveMouseItem2(id_lock)
	local item_mouse = getMouseItem()
	local item_inventory
	local entity_lock = findEntity(id_lock)
	
	if (item_mouse == nil) then
		-- go through all champions
		for i = 1, 4 do
			local champion = party.party:getChampionByOrdinal(i)
			
			if (champion) then
				-- go through the champion's inventory
				for j = 1, 32 do
					item_inventory = champion:getItem(j)
					
					if (item_inventory) then
						if (item_inventory:hasTrait("key")) and (entity_lock.lock:getOpenedBy() == item_inventory.go.name) then
							-- matching key found
							setMouseItem(item_inventory)
							champion:removeItemFromSlot(j)
							return
						elseif (item_inventory.go.containeritem) then
							-- the item is a container
							local key_found = findKeyInContainer(item_inventory, id_lock)
							
							if (key_found) then
								return
							end
						end
					end
				end
			end
		end
	end
end
It searches recursively through the inventories of all champions.
That means the key can be inside a container which is inside another container.
E.g. a key inside a keyring inside a wooden box.
User avatar
Isaac
Posts: 3192
Joined: Fri Mar 02, 2012 10:02 pm

Re: Keyring + Smart Locks

Post by Isaac »

Hmmm... This one approaches auto-playing the game, and it would detect any key that was [deliberately] not an obvious a key... So the player might click on a smart lock and have that bit of the puzzle solved for them.

*I like the idea of the key ring. It was really good in Arx Fatalis.
minmay
Posts: 2790
Joined: Mon Sep 23, 2013 2:24 am

Re: Keyring + Smart Locks

Post by minmay »

Isaac wrote:Hmmm... This one approaches auto-playing the game, and it would detect any key that was [deliberately] not an obvious a key... So the player might click on a smart lock and have that bit of the puzzle solved for them.
Um... so don't make that lock a smart lock?
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.
Post Reply