Ask a simple question, get a simple answer

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!
User avatar
Isaac
Posts: 3185
Joined: Fri Mar 02, 2012 10:02 pm

Re: Ask a simple question, get a simple answer

Post by Isaac »

minmay wrote: Thu May 16, 2019 10:28 am Unfortunately that won't work because lever connectors pass the LeverComponent as the first argument if connected to a ScriptControllerComponent, and you can't pass a component through a delayedCall without breaking save games. So you are either not imitating the connector correctly (which will break people's mods) or you are breaking save games (which will break people's mods).
Can you elaborate (more than you already did). What exactly breaks? I ask because part of the testing I did was to flip the lever, then immediately save the game during the delay, then I reloaded it, and the delay seemed to work just fine. Two seconds after the game loaded, the door opened—as expected. And after this, I closed the game entirely, then started it up, and loaded the save game again, and two seconds afterwards the delayed door opened. So...what have I missed?
oldboy87 wrote: Thu May 16, 2019 10:15 am I also wondered if having them placed right at the edge of the map was causing the issue but I did also try pushing them in a tile and the same thing happens.
AFAIK they are intended to only be used at the edge of the map, and the destination is always one tile away from the edge of the destination map.

On my map (0,0,0), I first placed the exit, and then walked through it in the preview... Then looked on the editor map to see where the party arrives on the second map (1,0,0), and I placed the first invisible teleporter on that tile; setting the desired arrival position as the teleporter's destination. At that point the exit works one way.

On the second map, I placed the second exit (adjacent to the [teleporter destination] arrival tile, and leading back to the first map). Once again in the preview, I pass the party through the second exit and now check where they arrive on first map. I put the second invisible teleporter there, and set its destination to just adjacent to the exit, where the party would normally emerge. Then the exits work in both directions.
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Re: Ask a simple question, get a simple answer

Post by minmay »

Isaac wrote: Thu May 16, 2019 11:55 am
minmay wrote: Thu May 16, 2019 10:28 am Unfortunately that won't work because lever connectors pass the LeverComponent as the first argument if connected to a ScriptControllerComponent, and you can't pass a component through a delayedCall without breaking save games. So you are either not imitating the connector correctly (which will break people's mods) or you are breaking save games (which will break people's mods).
Can you elaborate (more than you already did). What exactly breaks? I ask because part of the testing I did was to flip the lever, then immediately save the game during the delay, then I reloaded it, and the delay seemed to work just fine. Two seconds after the game loaded, the door opened—as expected. And after this, I closed the game entirely, then started it up, and loaded the save game again, and two seconds afterwards the delayed door opened. So...what have I missed?
Currently you are not imitating the connector correctly (which will break people's mods) because lever connectors connected to a ScriptControllerComponent pass the lever as an argument to the corresponding ScriptComponent function, and your code does not pass the lever as an argument to the corresponding ScriptComponent function.
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
vanblam
Posts: 243
Joined: Sun Nov 09, 2014 12:15 am

Re: Ask a simple question, get a simple answer

Post by vanblam »

minmay wrote: Thu May 16, 2019 10:28 am Unfortunately that won't work because lever connectors pass the LeverComponent as the first argument if connected to a ScriptControllerComponent, and you can't pass a component through a delayedCall without breaking save games. So you are either not imitating the connector correctly (which will break people's mods) or you are breaking save games (which will break people's mods).
You must use LeverComponent:toggle() to handle the connectors. There is no other way.

A disabled LeverComponent won't trigger its connectors. So the simplest way to do this is to start it out disabled, and have your onAnimationEvent hook enable it, toggle it, then disable it again. And remember to disable the ClickableComponent during this so that the player can't toggle the lever again themselves and mess it up. Like this:

Code: Select all

defineObject{
	name = "rc_stone_ground_lever",
	components = {
		{
			class = "Model",
			model = "mod_assets/vanblam/red_cave/models/env/rc_stone_ground_lever.fbx",
			staticShadow = true,
		},
		{
			class = "Animation",
			animations = {
				activate = "mod_assets/vanblam/red_cave/animations/rc_stone_ground_lever_activate.fbx",
				deactivate = "mod_assets/vanblam/red_cave/animations/rc_stone_ground_lever_deactivate.fbx",
			},
			onAnimationEvent = function(self, event)
				if event == "rc_sgl_start" then
					self.go.clickable:disable()
				elseif event == "rc_sgl_activate" then
					self.go.lever_hit1:restart()
					self.go.lever:enable()
					self.go.lever:toggle()
					self.go.lever:disable()
					self.go.clickable:enable()
				elseif event == "rc_sgl_deactivate" then
					self.go.lever_hit2:restart()
				end
			end,
		},
		{
			class = "Clickable",
			offset = vec(0,0.9,-0.7),
			size = vec(1, 0.6, 0.2),
			maxDistance = 1,
			--debugDraw = true,
		},
		{
			class = "Lever",
			sound = "rc_stone_lever",
			enabled = false,
		},
		{
				class = "Particle",
				name = "lever_hit1",
				offset = vec(-0.158,0.54,-0.7),
				particleSystem = "rc_hit_lever",
		},
		{
				class = "Particle",
				name = "lever_hit2",
				offset = vec(0.155,0.54,-0.7),
				particleSystem = "rc_hit_lever",
		},
	},
	placement = "wall",
	editorIcon = 12,
	tags = { "red cave", "vanblam" },
}
defineAnimationEvent{
		animation = "mod_assets/vanblam/red_cave/animations/rc_stone_ground_lever_activate.fbx",
		event = "rc_sgl_start",
		frame = 0,
}
defineAnimationEvent{
		animation = "mod_assets/vanblam/red_cave/animations/rc_stone_ground_lever_activate.fbx",
		event = "rc_sgl_activate",
		frame = 19,
}
defineAnimationEvent{
		animation = "mod_assets/vanblam/red_cave/animations/rc_stone_ground_lever_deactivate.fbx",
		event = "rc_sgl_deactivate",
		frame = 19,
}
(warning I was too lazy to actually test this specific code)

Note that this will only trigger the lever's onToggle and onDeactivate connectors, not its onActivate connectors. You might prefer to leave the LeverComponent enabled the whole time instead, which will make it trigger its onActivate and onToggle connectors when initially pulled, and its onDeactivate and onToggle connectors when it flips back up.
I was thinking the same as you were on this by disabling the the lever and having the animation event do the work. But on testing the lever it will switch over then switch back right after, but other then that it works as intended :). I'll see if I can mess with it a little to make it only do the activation animation. But thank you so much, that was exactly what I was looking to do :D

NOTE: It looks like its "self.go.lever:toggle()" that will cause the switch to switch over then switch back.

NOTE2: Its crazy that the only method you call there is toggle. Open or activate are nill values, hmm I wonder what the activate method is, because there has to be one right? Its just not called activate? Because I'm sure if the activate method was use instead of toggle it would just switch over once and then it would activate the target.
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Re: Ask a simple question, get a simple answer

Post by minmay »

vanblam wrote: Fri May 17, 2019 1:28 amNOTE2: Its crazy that the only method you call there is toggle. Open or activate are nill values, hmm I wonder what the activate method is, because there has to be one right? Its just not called activate?
It's toggle. LeverComponent:toggle() pulls the lever down if it's currently up, or pulls the lever up if it's currently down.

What would an activate() method do if the lever was already down?
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
vanblam
Posts: 243
Joined: Sun Nov 09, 2014 12:15 am

Re: Ask a simple question, get a simple answer

Post by vanblam »

minmay wrote: Fri May 17, 2019 5:56 am
vanblam wrote: Fri May 17, 2019 1:28 amNOTE2: Its crazy that the only method you call there is toggle. Open or activate are nill values, hmm I wonder what the activate method is, because there has to be one right? Its just not called activate?
It's toggle. LeverComponent:toggle() pulls the lever down if it's currently up, or pulls the lever up if it's currently down.

What would an activate() method do if the lever was already down?
Yea I see what you mean, the method would have to be universal for on or off, hence toggle. So there is no way( with out doing it in the editor) to have it switch once on a delay, then activate its target without breaking the mod/save.
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Re: Ask a simple question, get a simple answer

Post by minmay »

vanblam wrote: Fri May 17, 2019 6:58 amYea I see what you mean, the method would have to be universal for on or off, hence toggle. So there is no way( with out doing it in the editor) to have it switch once on a delay, then activate its target without breaking the mod/save.
Oh, I see. You can do this, but it is substantially more of a pain. I did something very similar in the Dungeon Master Resource conversion:

Code: Select all

-- NOTE: This object has to spawn an extra object to work, which is generally
-- invisible to both the player and designer. However, it means that extra care
-- is required if you wish to destroy this object.
defineObject{
	name = "dm_conduit_wheel",
	components = {
		{
			class = "Lever",
			sound = "dm_silence",
			onInit = function(self)
				spawn("dm_conduit_wheel_helper",self.go.level,self.go.x,self.go.y,self.go.facing,self.go.elevation,"wh"..self.go.id)
			end,
		},
		{
			class = "Controller",
			onActivate = function(self)
				if self.go.lever:isDeactivated() then
					self.go.lever:toggle()
				end
			end,
			onDeactivate = function(self)
				if self.go.lever:isActivated() then
					self.go.lever:toggle()
				end
			end,
			onToggle = function(self)
				self.go.lever:toggle()
			end,
			onInitialActivate = function(self)
				local helper = findEntity("wh"..self.go.id)
				if helper then
					helper.animation:play("activate")
				else
					Console.warn("no wheel helper found for "..self.go.id)
				end
			end,
			onInitialDeactivate = function(self)

			end,
		},
		{
			class = "Model",
			model = "mod_assets/dmcsb_pack/models/nil.fbx",
			enabled = false,
		},
		{
			class = "Animation",
			animations = {
				activate = "mod_assets/dmcsb_pack/animations/nil.fbx",
				deactivate = "mod_assets/dmcsb_pack/animations/nil.fbx",
			},
			enabled = false,
		},
	},
	placement = "wall",
	editorIcon = 12,
	tags = {"dm","dm_user"},
}
defineObject{
	name = "dm_conduit_wheel_helper",
	placement = "wall",
	components = {
		{ -- dummy for lever animation
			class = "Model",
			model = "mod_assets/dmcsb_pack/models/env/dm_lever_wheel.fbx",
		},
		{
			class = "Animation",
			animations = {
				activate = "mod_assets/dmcsb_pack/animations/env/dm_wheel_counterclockwise.fbx",
				deactivate = "mod_assets/dmcsb_pack/animations/env/dm_wheel_clockwise.fbx",
			},
			onAnimationEvent = function(self, event)
				if event:find("dm_wheel_end") then
					self.go.clickable:enable()
				elseif event:find("dm_wheel_start") then
					self.go.clickable:disable()
				elseif event:find("dm_wheel_on") then
					local main = findEntity(self.go.id:sub(3))
					if main then
						main.controller:activate()
					else
						Console.warn("no lever found for wheel "..self.go.id)
					end
				elseif event:find("dm_wheel_off") then
					local main = findEntity(self.go.id:sub(3))
					if main then
						main.controller:deactivate()
					else
						Console.warn("no parent conduit found for wheel helper "..self.go.id)
					end
				end
			end,
		},
		{
			class = "Clickable",
			offset = vec(0,1.37774,0),
			size = vec(0.8,0.8,0.2),
			onClick = function(self)
				local main = findEntity(self.go.id:sub(3))
				if main then
					if main.lever:isActivated() then
						self.go.animation:play("deactivate")
					else
						self.go.animation:play("activate")
					end
				else
					Console.warn("no parent conduit found for wheel helper "..self.go.id)
				end
			end,
		},
	},
}
It works as you'd expect in the editor, you only have to place the dm_conduit_wheel object, connectors work like a regular lever but with the delay, etc. But the implementation under the hood is quite nasty.
In retrospect, I didn't actually need to use this two-object approach; it could all be done in one object if I did more manual animation management.
The big issue is, if there's a ClickableComponent and a LeverComponent on the same object, clicking the ClickableComponent is going to toggle the LeverComponent and play its animation, even if the LeverComponent is disabled. But I think you should be able to prevent that by putting a dummy component that does something on clicks (like ButtonComponent or another LeverComponent) before the LeverComponent, and that dummy component will "catch" the click before the lever does, and then you can do all the lever manipulation with the ClickableComponent's onClick hook and animation events, like in the object above.
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
Isaac
Posts: 3185
Joined: Fri Mar 02, 2012 10:02 pm

Re: Ask a simple question, get a simple answer

Post by Isaac »

minmay wrote: Thu May 16, 2019 9:28 pmCurrently you are not imitating the connector correctly (which will break people's mods) because lever connectors connected to a ScriptControllerComponent pass the lever as an argument to the corresponding ScriptComponent function, and your code does not pass the lever as an argument to the corresponding ScriptComponent function.
That made sense; and you are right; thanks. I was more concerned with the [seemingly] scriptless delay, and did not consider custom scripts expecting the lever component for their own logic.

*But it does occur to me that anyone making custom scripts would probably not be confused by it; nor unable to modify their mod to accommodate it, or work around it; since it is an inherently nonstandard feature.

Still it is always best not to screw up standard behaviors.


@Vanblam
Did I mistake your intention with the delay, in that it was the lever itself that you wanted delayed—rather than a delay on the trigger action?
User avatar
vanblam
Posts: 243
Joined: Sun Nov 09, 2014 12:15 am

Re: Ask a simple question, get a simple answer

Post by vanblam »

Isaac wrote: Fri May 17, 2019 11:37 pm @Vanblam
Did I mistake your intention with the delay, in that it was the lever itself that you wanted delayed—rather than a delay on the trigger action?
You had the right intention I wanted the lever to delay its action on the target because the animation is slower, so when I hit the switch, the door (or any target) started opening too quickly and it looked odd.

@minmay
Wow that is a genius idea I love it :D. I can't wait to play around with that.

Red Cave is so close to being finished. Ill send it to anyone who wants to "play test it" when I'm done to see if everything looks good before I release it.
User avatar
vanblam
Posts: 243
Joined: Sun Nov 09, 2014 12:15 am

Re: Ask a simple question, get a simple answer

Post by vanblam »

Hey guys I have another question for ya, I have a wall that needs to check the adjacent (either left side or right side) wall to see if it is facing the same direction and if so it will spawn an object. If the wall next to it is not facing the same direction spawn this object instead. If I could get it to spawn based on the direction that would be even better ( example: inside corner vs outside corner).

Image
Badgert
Posts: 258
Joined: Sun Jan 29, 2017 6:14 pm

Re: Ask a simple question, get a simple answer

Post by Badgert »

Can you please tell whether it is possible to make it so that night is always on a single location? And if so, how?
Post Reply