Moving item from one pedestial to the next

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!
Torquemada
Posts: 25
Joined: Fri Apr 05, 2013 10:52 pm

Re: Moving item from one pedestial to the next

Post by Torquemada » Wed Jul 25, 2018 12:11 am

I found out what was wrong, each time I put something on the pedestal the onInsert trigger is fired, each time it checks for the right offering, if offerings are correct, all items are destroyed with delay and the target object spawned on the pedestal. The problem here is the spawned object also triggers the onTrigger event and because I am using destroyDelayed, the items are still on the pedestal because destroyDelayed doesn't actually destroy them until I exit the function. This causes the offer check to return true again and the process repeats itself indefinitely, each time adding a new item on the pedestal until the surface max capacity if filled up causing stack overflow.

Below is the simplified version of the code, fixed by adding an extra check item.go.name ~= "some_item"

Code: Select all

function onAddOffer(pedestal, item)
	local offer = getOffer(pedestal)
	if pedestal.go.id == "musket_payment_pedestal" and item.go.name ~= "some_item" then
		if offer == true then
			for i,v in pedestal.go.surface:contents() do
				v.go:destroyDelayed()
			end
			for i,v in reward_pedestal.surface:contents() do
				v.go:destroyDelayed()
			end
			pedestal.go.surface:addItem(spawn("some_item").item)
		end
	end
end

Zo Kath Ra
Posts: 829
Joined: Sat Apr 21, 2012 9:57 am
Location: Germany

Re: Moving item from one pedestial to the next

Post by Zo Kath Ra » Wed Jul 25, 2018 12:17 am

Torquemada wrote:
Tue Jul 24, 2018 8:54 pm
This is what I have currently:

Code: Select all

		for i,v in pedestial1.surface:contents() do
			v.go:destroyDelayed()
		end
		for i,v in pedestial2.surface:contents() do
			v.go:destroyDelayed()
		end
		pedestial1.surface:addItem(spawn("some_item").item) -- Here I get Stack overflow when run
I've modified your code, and it works (no stack overflow) in the editor when I call it with a floor trigger:

Code: Select all

function test()
	for i,v in altar_1.surface:contents() do
		v.go:destroyDelayed()
	end
	
	for i,v in altar_2.surface:contents() do
		v.go:destroyDelayed()
	end
	
	altar_1.surface:addItem(spawn("dagger").item)
end
The problem must be with the way you call your code.

Torquemada
Posts: 25
Joined: Fri Apr 05, 2013 10:52 pm

Re: Moving item from one pedestial to the next

Post by Torquemada » Wed Jul 25, 2018 12:19 am

Thank Zo Kath Ra

Yeah an oversight with onInsert trigger turned out to be the culprit, as seen in my reply above

Pompidom
Posts: 476
Joined: Sun May 06, 2018 9:42 pm

Re: Moving item from one pedestial to the next

Post by Pompidom » Wed Jul 25, 2018 12:26 am

I just created the stuff noobstyle as well :)

The 3 items required are a red gem a green gem and a blue gem.
And then the cheese gets transported from alcove 2 to alcove 1
alcove 1 connected to the 3 scripts

and counter that activates script 4

It's clunky but simple and works 100%

Code: Select all

function Quest1()
	local s = dungeon_alcove_1.surface
	for _,e in s:contents() do
		if e.go.name == "red_gem" then
			e.go:destroy()
			playSound("secret")
			counter_1.counter:decrement()
		end
	end
end

Code: Select all

function Quest2()
	local s = dungeon_alcove_1.surface
	for _,e in s:contents() do
		if e.go.name == "green_gem" then
			e.go:destroy()
			playSound("secret")
			counter_1.counter:decrement()
		end
	end
end

Code: Select all

function Quest3()
	local s = dungeon_alcove_1.surface
	for _,e in s:contents() do
		if e.go.name == "blue_gem" then
			e.go:destroy()
			playSound("secret")
			counter_1.counter:decrement()
		end
	end
end

Code: Select all

function Quest4()
	local s = dungeon_alcove_2.surface
	for _,e in s:contents() do
		if e.go.name == "cheese" then
			e.go:destroy()
			playSound("secret")
			dungeon_alcove_1.surface:addItem(spawn("cheese").item)
		end
	end
end

Pompidom
Posts: 476
Joined: Sun May 06, 2018 9:42 pm

Re: Moving item from one pedestial to the next

Post by Pompidom » Wed Jul 25, 2018 12:46 am

I'm trying to recreate your solution, but it's over my head :)

I'm just glad my noob solution works as well.

Torquemada
Posts: 25
Joined: Fri Apr 05, 2013 10:52 pm

Re: Moving item from one pedestial to the next

Post by Torquemada » Wed Jul 25, 2018 4:46 pm

Pompidom wrote:
Wed Jul 25, 2018 12:46 am
I'm trying to recreate your solution, but it's over my head :)

I'm just glad my noob solution works as well.
It's basicly same solution for me except I am destroying more than one item (wiping everything I might have put on the altar). I am also using the same function for more than one pedestal by using <source, item> parameters in onInsertItem

User avatar
akroma222
Posts: 1024
Joined: Thu Oct 04, 2012 10:08 am

Re: Moving item from one pedestial to the next

Post by akroma222 » Thu Jul 26, 2018 6:27 am

It wouldnt hurt for this thread to contain a link to Information re spawning / destroying items on surfaces :
viewtopic.php?f=22&t=13948&hilit=infodump
This is minmays info dump (there is alot to read) - so here is the stuff relevant here (I think) :
SpoilerShow
Important: On a map vs. not on the map

When you start Isle of Nex, you're in a cage with a branch in front of you. This branch is on the map. On every frame, its components are updated and its ModelComponent is drawn. If you leave the level, it will still be on a map, just not on the current map. Its components will still update, but not every frame (maps other than the current one take turns updating, so it might update every second frame or every 500th frame depending on how many maps the dungeon has and how how close you are to the map), and it won't be drawn.
However, when you pick up that branch, and it becomes the mouse item, it is removed from the map. The GameObject's 'map' field is set to nil, and its components stop updating. Furthermore:
- The object cannot be retrieved by findEntity() if it is not on a map.
- Obviously, since it is not on any map, Map:entitiesAt() and Map:allEntities() won't find it either.
- The following methods will CRASH if you call them with an object that is not on a map:
GameObject:destroy()
GameObject:destroyDelayed()
GameObject:getPosition()
GameObject:getSubtileOffset()
GameObject:removeAllComponents()
GameObject:removeComponent()
GameObject:setPosition()
GameObject:setSubtileOffset()
GameObject:setWorldPosition()
GameObject:spawn()
Various component methods
Interestingly, GameObject:getElevation(), GameObject:getWorldPosition(), GameObject:getWorldPositionY(), and GameObject:getWorldRotation() will still return the position/rotation from when the object was last on a map, which I suppose has vague potential to be useful. Also, notice that GameObject:setWorldPositionY() and GameObject:setWorldRotation() and its derivatives still work on objects that are not on a map, but since calling them won't add the object back to a map, it's hard to think of a use for this.
- If an object not on a map has any connectors, and it triggers them, it will crash, because triggering a connector uses the triggering object's map. The raw hooks in the component definition will still work; they don't use the map. This means that you can imitate connector functionality without needing a map. There is already a Grimrock 2 library that can do this: viewtopic.php?f=22&t=8345
- An object that is not on a map will not run any of its components' onInit() hooks. Even if the object is added to a map later, the onInit() hooks will still not run. Hooks like ControllerComponent.onInitialActivate() will still work, however.
- An object that is not on a map does not enforce its unique id. If there is an object in a champin's inventory with the id "rock1", and you spawn a new object with the id "rock1", it won't crash; you'll end up with two objects that both have the id "rock1". You should NEVER do this, because it will result in a crash when the game is saved and loaded.
- The savegame code will not directly reach an object that is not on a map, but since it will reach inside containers and inventories, this is really only relevant when you do something that renders an object completely unretrievable like removing it from an inventory without returning it to the map (or another inventory). This is a good thing; it acts as a sort of "garbage collection" for those objects when the game is saved and loaded.

Inventories, surfaces, and sockets
It is important that, across all inventories, SurfaceComponents, SocketComponents, ContainerItemComponents, etc., you maintain the discipline of having only *one* reference to an object at a time. Should two SurfaceComponents (or two inventories, or an inventory and a SocketComponent, etc.) have a reference to the same item at the same time, one of the following will happen when the game is saved and loaded:
1. If the game is able to find the item, it will probably duplicate the object. This is obviously not something you want.
2. If the game is not able to find the item, it will crash. This is also not something you want.
Moving an item, adding it to the map, or adding it to another inventory or surface or socket or container does NOT remove it from its current inventory or surface or socket or container, if it has one. You need to either formally remove the item with removeItem(), destroy the item's object, or destroy the surface or inventory. The party using the mouse to pick up an item will also remove it from its surface or socket if it has one, but you obviously do not have the ability to make that happen with the scripting interface.
This poses a problem with SurfaceComponent because it lacks a removeItem() method. To remove an item from a SurfaceComponent via script, you must either:
1. Destroy the ItemComponent with GameObject:removeComponent(). By extension, GameObject:removeAllComponents(), GameObject:destroy(), and GameObject:destroyDelayed() will also do the job, but since those (essentially) destroy the entire object, it's probably not what you were looking for.
2. Destroy the SurfaceComponent in the same way.

Option 1. is preferable, since option 2. removes *all* items from the surface. But it's still almost useless, because after destroying the ItemComponent, you can't get it back. You can copy most of the properties of the original ItemComponent to the new one, but you cannot copy the onThrowAttackHitMonster, onEquipItem, or onUnequipItem hooks; you would need to spawn a completely new object to get the hooks back. You will encounter the same problem if you try to use option 2.

If an object has an ItemComponent, you can easily remove it from the map by adding that ItemComponent to a champion or monster's inventory, or a ContainerItemComponent, or setting it as the mouse item. However, once again, THIS DOES NOT REMOVE THE ITEM FROM SURFACES OR SOCKETS, and I just explained why that is a big problem.
Remember that you can check if an object is in a surface, container, etc. by checking GameObject:getFullId(). But make sure that you do this BEFORE you add the object to an inventory or whatever, because that will reset the full id, even though the ItemComponent is actually still in a surface/container/whatever!

So, the short version of all this is: Do not, under any circumstances, move an item that might be in a surface.

P.S. Destroying a sack does not destroy the items inside it, it just renders them inaccessible (they will be effectively "destroyed" when the game is saved and reloaded, like items that are not in any map or inventory).
Also, you can condense those 4 functions into 1 like this :
(tested by dropping a blue gem into surface then stepping on a floortrigger connected to the Quest function)
SpoilerShow

Code: Select all

acceptedItemTable = {"red_gem", "green_gem", "blue_gem"}

function Quest()
	
	local surf = dungeon_alcove_1.surface
	
	for _, gem in surf:contents() do
		
		for k,v in pairs(acceptedItemTable) do
		
			if gem and v and gem.go.name == v then
		
				gem.go:destroyDelayed()
			
				playSound("secret")
			
				counter_1.counter:decrement()
			end
		end
	end
end
Hope this helps :-)
Last edited by akroma222 on Fri Jul 27, 2018 1:06 am, edited 1 time in total.

Pompidom
Posts: 476
Joined: Sun May 06, 2018 9:42 pm

Re: Moving item from one pedestial to the next

Post by Pompidom » Thu Jul 26, 2018 10:20 am

This will surely help. Also to make sure I don't end up with a broken mod ;)

minmay
Posts: 2656
Joined: Mon Sep 23, 2013 2:24 am

Re: Moving item from one pedestial to the next

Post by minmay » Thu Jul 26, 2018 7:45 pm

Just a note, destroying the SurfaceComponent doesn't work - the items in it still end up tainted. You have to destroy the items. I should go back and edit that post.

Also, once again, akroma, use destroyDelayed() here, not destroy().
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
akroma222
Posts: 1024
Joined: Thu Oct 04, 2012 10:08 am

Re: Moving item from one pedestial to the next

Post by akroma222 » Thu Jul 26, 2018 7:56 pm

minmay wrote:
Thu Jul 26, 2018 7:45 pm
Also, once again, akroma, use destroyDelayed() here, not destroy().
Ahh! Indeed I should have :oops: Cheers!

Post Reply