Help with a "check for item" script

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

Help with a "check for item" script

Post by akroma222 »

Hey all,
I am sure someone can point out to me quickly what Ive been doing wrong here...
I just want the item to either:
- stay as the mouse item
- or if thats not possible be spawned into champs inventory
- or if thats not possible just dropped on the ground
Some champs will have a "light equipment" trait meaning they can not equip heavy armour and weapons...

And the part that has taken me so much time is the dropping on the ground bit!! Geezz :(
So I am pretty sure its almost ok, but yeah help please!!
SpoilerShow

Code: Select all

defineTrait{
	name = "light_equipment",
	uiName = "Light Equipment",
	icon = 45,
	charGen = false,
	description = "Can not carry heavy stuff.",
	onRecomputeStats = function(champion, level)
		if level > 0 then
			local heavyItem = nil
			for slot = 1,ItemSlot.OffHand2 do
				local item = champion:getItem(slot)
				if item then
					if item:hasTrait("heavy_weapon") 
					or item:hasTrait("heavy_armour") then
						heavyItem = champion:getItem(slot)
						local stackSize = nil
						if heavyItem:getStackable() then

							stackSize = heavyItem:getStackSize()
						end 
						local m = getMouseItem()
						if m == nil then
							champion:removeItemFromSlot(slot)

							if stackSize then

								heavyItem:setStackSize(stackSize)
             
							end 
							setMouseItem(heavyItem)
							playSound("item_drop")
            
							hudPrint("Can not carry heavy stuff.")
							return false
						else 
							heavyItem = champion:getItem(slot)
							local newItem = spawn(heavyItem)
							if heavyItem then
								heavyItem:destroy()
							end
							
                                                        local slot2 = nil
							for slot2 = 1, 32 do
            
								if champion:getItem(slot2) == nil then
									if slot2 ~= nil then
										champion:insertItem(slot2, newItem )
										playSound("item_drop")

										hudPrint("Can not carry heavy stuff.")
										return false
									else
										spawn( newItem , party.level, party.x , party.y, party.facing ,party.elevation)
 
										playSound("item_drop")
            
										hudPrint("Can not carry heavy stuff.")
										return false
									end
								end
							end
						end
					end
				end
			end
		end
	end,
}
User avatar
akroma222
Posts: 1029
Joined: Thu Oct 04, 2012 10:08 am

Re: Help with a "check for item" script

Post by akroma222 »

Ok, think I might have it now...
------------------------------------------------------------------------------------------------------------------------------------------
EDIT - and no we dont! DO NOT USE THIS SCRIPT it spawns and destroys items onequip and I can not vouch for your safety.
Working on alternate solutions now.... again, DO NOT USE THIS SCRIPT
----------------------------------------------------------------------------------------------------------------------------------------
Here is the trait 'faerie_equipment' (they can only equip faerie stuff, right??)
SpoilerShow

Code: Select all

defineTrait{
	name = "faerie_equipment",
	uiName = "Faerie Equipment",
	icon = 45,
	charGen = false,
	description = "Faeries can only wear and wield special items.",
	onRecomputeStats = function(champion, level)
		if level > 0 then
			
			for slot1 = 1 , 12 do
				local item1 = champion:getItem(slot1)
				if item1 then
					if not item1.go.item:hasTrait("faerie") then
						local nonFaeItem = champion:getItem(slot1)
						if nonFaeItem:getStackable() then

							local stackSize = nonFaeItem:getStackSize()
						end
						if getMouseItem() == nil then
							champion:removeItemFromSlot(slot1)
							if stackSize ~= nil then
		
								nonFaeItem:setStackSize(stackSize)
	
							end

							delayedCall("raceClassScript", 0.1, "spawnHeavyMouse", nonFaeItem.go.name)
                   					playSound("item_drop")
            
							hudPrint("Faeries can only wear and wield special items.")
							return
                   				else
                   					for slot2 = 13, 32 do

								local item2 = champion:getItem(slot2)            
								if item2 == nil then
									local s = slot2
									local champ = champion
									champion:removeItemFromSlot(slot1)

									if stackSize ~= nil then
		
										nonFaeItem:setStackSize(stackSize)
	
									end
									delayedCall("raceClassScript", 0.1, "spawnHeavyInvent", nonFaeItem.go.name, champ, s)
                   							playSound("item_drop")
            
									hudPrint("Faeries can only wear and wield special items.")
									return
								else
									champion:removeItemFromSlot(slot1)

									if stackSize ~= nil then
		
										nonFaeItem:setStackSize(stackSize)
	
									end
									delayedCall("raceClassScript", 0.1, "spawnHeavyFloor", nonFaeItem.go.name)
                   							playSound("item_drop")
            
									hudPrint("Faeries can only wear and wield special items.")
									return
								end
							end
						end
					end
				end
			end
		end
	end,
}
All you need to do is give some equipment the "faerie" trait to able to equip it... or not
onRecomputeStats does some sorting - as to whether you mouse is free of items or whether you have a full inventory
It then calls a script in dungeon (raceClassScript) with 3 different delayedCall functions (one for each scenario)
1. Mouse is free and the non faerie item just bounces back to the mouse...
2. Mouse is occupied as a result of you equipping AND you have free slots in your inventory, where it is then spawned
3. Mouse and inventory are all full and it is spawned on the ground in front of you.

Put this into the script entity (raceClassScript)
SpoilerShow

Code: Select all

function spawnHeavyMouse(itemName)
	local heavyItem = spawn(itemName)
    setMouseItem(heavyItem.item)
end

function spawnHeavyInvent(itemName, champion, slot)
	local heavyItem = spawn(itemName)
	local s = slot
	local c = champion
	c:insertItem(s, heavyItem.item)  
end

function spawnHeavyFloor(itemName)
    spawn(itemName,party.level,party.x,party.y,party.facing,party.elevation)
end
And now we have way to make special restrictions on equipment for race /class :lol:
...I think...
Last edited by akroma222 on Mon Jan 05, 2015 8:38 am, edited 2 times in total.
minmay
Posts: 2789
Joined: Mon Sep 23, 2013 2:24 am

Re: Help with a "check for item" script

Post by minmay »

I have several objections to the way this works I'm afraid.
- it destroys/spawns objects, this is a huge problem. It means the state of the item will not be preserved. Remember that the object could have almost any of these components. If a single one of them has changed state in any way since it was originally spawned, your script will break it.
- you aren't even preserving the object's original id
- if the player does anything between the delayedCall calls and their resolution, it will screw everything up

Fortunately, there is a very easy way to accomplish something very close to what you want. Simply add this component to the object definition:

Code: Select all

{
	class = "RunePanel",
	requirements = {"skill_goes_here",5},
	onAttack = function() return false end,
},
If an item has a RunePanelComponent as its primary action and its requirements are not met, none (?) of the EquipmentItem benefits will be conferred to the user. Armour won't give protection, resistances, and so on. Overwriting RunePanelComponent's onAttack prevents someone from actually using the item to cast spells if they meet the requirements (nothing happens when they click on it).

Now, the "requirements" entry only works for skill requirements. But if you want to require a trait, or anything else arbitrary, you can add something like this to your ItemComponent definition:

Code: Select all

onEquipItem = function(self, champion, slot)
  if (championMeetsRequirements) then
    self.go.runepanel:setRequirements({})
  end
end,
onUnequipItem = function(self, champion, slot) self.go.runepanel:setRequirements({"impossible",1}) end,
This will eliminate the runepanel's requirements if the champion meets your arbitrary requirements, allowing them to benefit from the equipped item. It restores them when the item is unequipped.
This approach does have the undesirable side effect of adding junk to the item tooltip. It'll show "Requires ???" if the requirements ask for a skill that doesn't actually exist, and if the requirements don't ask for anything it'll just show "Requires". No, setRequirements(nil) doesn't work. But it is much more flexible and safer than your approach.
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: 1029
Joined: Thu Oct 04, 2012 10:08 am

Re: Help with a "check for item" script

Post by akroma222 »

That is certainly a very cool work around!
You are right, there are holes in this system.
I am going to try and tidy it up a bit, make sure I preserve as much as possible (original ID's, wand charges etc)
Im sure it would be possible to create a table of the items components before all the destroying and respawning goes down..
It does sound like a big effort for a minor feature though!

Thanks for dropping in good sir, I will try out your methods tonight :D

EDIT your links just sends me to the main list of all components :lol: :lol: point well made
minmay
Posts: 2789
Joined: Mon Sep 23, 2013 2:24 am

Re: Help with a "check for item" script

Post by minmay »

I'll say it again, I am certain that you cannot possibly get the destroying/spawning approach to work correctly. Even if you wanted to account for storing every component's state...well, you can't. You can't even get the name of existing components let alone all the state information, and many are completely impractical or downright impossible to keep track of manually. And what do you plan to do about all the components' onInit hooks getting run again?
If you really need the items to unequip themselves like in your original script, you can already trivially get rid of the spawning for some of your cases: if the champion has an empty inventory slot, you can just call Champion:swapItems() with the empty slot and the slot that the item was equipped into, and the item will be unequipped and moved to the empty slot. There's no need to destroy and respawn it.
Also, if it wasn't clear, I'm pretty sure you should really be doing this in onEquipItem, not onRecomputeStats.
I am not sure if it can be done cleanly if there are no free inventory slots. Here is the best thing I was able to come up with:

Code: Select all

			onEquipItem = function(self,champion,slot)
				if not (championMeetsRequirements) then
					game.frame.callNextFrame(function(champion,slot)
						local mouse = getMouseItem()
						setMouseItem(champion:getItem(slot))
						champion:removeItemFromSlot(slot)
						if mouse then champion:insertItem(slot,mouse) end
					end,{champion,slot})	
				end
			end,
Under the assumption that the player cannot register two mouse clicks in the same frame (in my testing, they can't!), this works. If a champion doesn't meet the requirements, the item will remain as the mouse item, and if it replaced an existing item, that item will stay in its slot. Unfortunately there is an ugly "flash" for 1 frame when the player attempts to equip the item.

(I have a script library that includes a function callNextFrame() to call another arbitrary function with arbitrary arguments, on the frame following the original call. Here is the relevant code:)
SpoilerShow
In a ScriptComponent named 'frame' on an entity named 'game':

Code: Select all

nextFrameFunctions = {}
function onFrame()
	for i = #nextFrameFunctions,1,-1 do
		nextFrameFunctions[i].hook(unpack(nextFrameFunctions[i].arg))
	end
	nextFrameFunctions = {}
end
-- Call function func next frame with arguments in table args. They are called
-- in the same order that callNextFrame is called.
function callNextFrame(func,args)
	table.insert(nextFrameFunctions,{hook=func,arg=args})
end
In the definition for the 'party' object:

Code: Select all

		{	-- This will trigger once every frame, unless the game is running at over 10000 FPS. (Timers do not trigger more than once per frame.)
			class = "Timer",
			name = "frame_timer",
			timerInterval = 0.0001,
			onActivate = function(self)
				game.frame.onFrame()
			end,
		},
It is possible that there exists a way to replace the item later on the same frame, which would fix this issue. I'm not in the mood to look for one at the moment.
I really think the runepanel solution is better, even though it leaves the ugly "Requires" text. It's a lot safer since you don't have to worry about mouse item or equipping/unequipping order weirdness. And it's also more consistent with the original game; you can wield weapons that you don't meet the requirements for, just not use them. So it's a natural extension of that that you can put on armour/jewellery/etc that you don't meet the requirements for, just not benefit from its protection/resistances/bonuses/etc.

edit: greatly improved script
Last edited by minmay on Mon Jan 05, 2015 9:26 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
akroma222
Posts: 1029
Joined: Thu Oct 04, 2012 10:08 am

Re: Help with a "check for item" script

Post by akroma222 »

Wow that is some pretty impressive micro second scripty stuff right there

Of course, you are totally right - spawning and destroying things over and over when I dont have to is ridiculous,
it would probably have catastrophic effects down the line :o After reading your last post I had a pick through some of the systems I have in place already...
... and yes, big catastrophic FAIL :lol:
Thank you for puling me up before this idea did anyone's mod some serious harm heheh

I have started playing around with your runepanel method, seems a little restrictive and tricky but it definitely works as it should!
Your right, it will be good for what I had in mind... have you created any assets using that method?
(I should probably just settle for a faerie hybrid race that can use the same gear as everyone else... :roll: )

Thanks again champ
minmay
Posts: 2789
Joined: Mon Sep 23, 2013 2:24 am

Re: Help with a "check for item" script

Post by minmay »

akroma222 wrote:Your right, it will be good for what I had in mind... have you created any assets using that method?
I've tested it, yes. While it's kind of a silly hack, it should be very stable since the main game relies on most of the same behaviour: you don't get the stat bonuses from magic wands that you can't use. If there were a way to circumvent the requirements on wands, it would be a major bug in the original game. And it definitely works fine on slots other than weapon slots, you even get the red background.

EDIT: Upon further reflection, I am an idiot. The alternate method I posted does *not* require storing an item reference. I've updated the post. The only problem remaining is the 1-frame "flash".
Last edited by minmay on Mon Jan 05, 2015 9: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
akroma222
Posts: 1029
Joined: Thu Oct 04, 2012 10:08 am

Re: Help with a "check for item" script

Post by akroma222 »

minmay wrote:
akroma222 wrote:Your right, it will be good for what I had in mind... have you created any assets using that method?
I've tested it, yes. While it's kind of a silly hack, it should be very stable since the main game relies on most of the same behaviour: you don't get the stat bonuses from magic wands that you can't use. If there were a way to circumvent the requirements on wands, it would be a major bug in the original game. And it definitely works fine on slots other than weapon slots, you even get the red background.
Stable is Brilliant!!
Do you know what a silly hack is...?? Blowing up and respawning things over and over :lol: :lol: oh dear..
Thanks again minmay, you always save the day ;)
User avatar
JKos
Posts: 464
Joined: Wed Sep 12, 2012 10:03 pm
Location: Finland
Contact:

Re: Help with a "check for item" script

Post by JKos »

Just thought to tell you that I have noticed that if you try to add an item to a already filled slot the item will automatically drop to the ground.
You can test this easily by calling this multiple times:

party.party:getChampion(1):insertItem(1,spawn('dagger').item)

Not sure if this is helping you, I didn't read those scripts with thought.
- LoG Framework 2http://sites.google.com/site/jkoslog2 Define hooks in runtime by entity.name or entity.id + multiple hooks support.
- cloneObject viewtopic.php?f=22&t=8450
User avatar
akroma222
Posts: 1029
Joined: Thu Oct 04, 2012 10:08 am

Re: Help with a "check for item" script

Post by akroma222 »

I am certain that it will help me jKos! Thankyou!

I really wish I knew this hours and hours ago hehehe :cry:
Post Reply