anything other than a wall to prevent player throwing stuff?

Talk about creating Grimrock 1 levels and mods here. Warning: forum contains spoilers!
User avatar
Komag
Posts: 3659
Joined: Sat Jul 28, 2012 4:55 pm
Location: Boston, USA

anything other than a wall to prevent player throwing stuff?

Post by Komag »

In the original game you cannot throw things at walls or at the sockets/alcoves for the cube (when you're putting in the parts to wake it up) - I cannot seem to replicate that.

Is there any object definition that will act like a wall so objects can't be thrown at it?

(I tried "replacesWall = true," but that just replaces the model and you can still throw at it. Also, secret walls don't work - you can throw at them too as they are just doors. Also, "repelProjectiles = true" doesn't help, you can still throw at it)
Finished Dungeons - complete mods to play
User avatar
Komag
Posts: 3659
Joined: Sat Jul 28, 2012 4:55 pm
Location: Boston, USA

Re: anything other than a wall to prevent player throwing st

Post by Komag »

Never got any takers on this? I discovered that if every champion in the party is disabled that you cannot throw anything (if you had something on the mouse before getting disabled). It's not really feasible to do that approach though!
Finished Dungeons - complete mods to play
User avatar
Edsploration
Posts: 104
Joined: Wed Sep 19, 2012 4:32 pm

Re: anything other than a wall to prevent player throwing st

Post by Edsploration »

I'll bite.

This looks like a job for alcoves!

...sort of. As I tried to solve this problem I realized there are a TON of corner-cases that are hard to plug up with alcoves. I got half way to a clean solution, but it's really difficult to make it work in every situation.

By the way, the following only covers left-mouse-click throws, not using throwing items.

My method works around dynamically spawning alcoves around the party as they move. If an item is "placed" in this alcove, the onInsertItem hook returns false preventing the item from being inserted. The item is still thrown but at least we know which item we have to block. We can make a call through a 0.01 second timer (before next frame) to destroy the thrown item and spawn a new copy of it on the mouse pointer.

STILL NEEDED: Proper item save/respawn logic keeping track of scroll text or any item with properties that must be set after it is spawned. I've encountered the need for this twice now so I may get around to writing this logic tree someday. The upcoming item.class property will help with this. Ideally we'd be able to set item x, y or move an item off the map directly via scripting though.

The other big problem is the alcove positions.
  • Alcoves placed tight in close to the party will prevent throw attempts while turning.
  • Alcoves with deep sensitive areas will prevent throwing while moving forward or backwards.
  • Alcoves set too far away from the party are not sensitive in their lower-left and lower-right corners (the farthest point from the camera).
  • Alcoves overlapping by position or depth with other alcoves will interfere with those other alcoves. This means we have to structure our alcove set to be behind sensitive regions for daemon eye sockets, wall alcoves, and alters (the hardest to accomplish).
Defining custom alcoves is complicated by the fact that their anchorPos and targetPos vectors are relative to the alcove's origin, but targetSize is NOT! So I defined two alcoves: one for throw attempts along the X-axis, and one for throw attempts along the Y-axis (which is Z-axis in 3-space in LoG). I also defined another set of tight alcoves around the party during movement (timed separately to move, turn, and Toorum mode move and turn).

Problems with the solution so far that are huge headaches and I don't want to deal with (now):
  1. Item properties are not preserved. Scroll text is lost, charges are lost, torch duration is reset, etc.
  2. Lower-edge of alcove rises above throw-sensitive region while backing up. A decently-timed, decently-positioned click can still throw while backing up. Lowering the alcove would block drop-sensitive space, so instead more (just 1 more maybe?) alcoves at different depths would have to be added and removed with key timings.
  3. Impossible to place an item on altars. Fixing this would take splitting each alcove into 3 for left, right, and deep center behind altar.
  4. May conflict with other custom alcoves. Pretty much must be dealt with on a case-by-case basis.
  5. Throw and drop sounds still play even when the item throw or drop attempt is blocked by the alcoves. (There is a small region where drop is also blocked to compensate for slight camera movements which happen sometimes and not other times. I have no idea why.)
Features!
:arrow: Throw-blocking which can be turned on and off by calling the preventThrows() and allowThrows() functions!
:arrow: Timing adjustments for Toorum! (untested... but he's exactly 2 times as fast, right?)
:arrow: Expanable! May add functionality for custom messages upon throw attempts, different logic for different items, etc. Perhaps you'd only like some item throws to be blocked. Or some to morph into doves after 0.5 seconds of flight?!

Custom Alcoves:

Code: Select all

defineObject{
	name = "no_throw_alcove_X",
	class = "Alcove",
	anchorPos = vec(0, 2, 0.5),
	targetPos = vec(0, 2, 0.5),
	targetSize = vec(0, 1.3, 5),
	placement = "wall",
	onInsertItem = function(self, item)
		no_throw_script:setItem(item)
		no_throw_timer:activate()
		return false
	end,
	editorIcon = 92,
}

defineObject{
	name = "no_throw_alcove_Y",
	class = "Alcove",
	anchorPos = vec(0, 2, 0.5),
	targetPos = vec(0, 2, 0.5),
	targetSize = vec(5, 1.3, 0),
	placement = "wall",
	onInsertItem = function(self, item)
		no_throw_script:setItem(item)
		no_throw_timer:activate()
		return false
	end,
	editorIcon = 92,
}

defineObject{
	name = "no_throw_movement_alcove_X",
	class = "Alcove",
	anchorPos = vec(0, 1.78, 0),
	targetPos = vec(0, 1.78, 0),
	targetSize = vec(1.9, 0.43, 3),
	placement = "wall",
	onInsertItem = function(self, item)
		no_throw_script:setItem(item)
		no_throw_timer:activate()
		return false
	end,
	editorIcon = 92,
}

defineObject{
	name = "no_throw_movement_alcove_Y",
	class = "Alcove",
	anchorPos = vec(0, 1.78, 0),
	targetPos = vec(0, 1.78, 0),
	targetSize = vec(3, 0.43, 1.9),
	placement = "wall",
	onInsertItem = function(self, item)
		no_throw_script:setItem(item)
		no_throw_timer:activate()
		return false
	end,
	editorIcon = 92,
}
Custom Party Hooks:

Code: Select all

cloneObject{
	name = "party",
	baseObject = "party",
	onMove = function(party, direction)
		no_throw_script:moveBlockers(direction)
	end,
	onTurn = function(party, direction)
		no_throw_script:turnBlockers()
	end,
}
Script Object (must have its id set to "no_throw_script"):

Code: Select all

throwingAllowed = true

toorumMode = 1	-- 1=no, 2=yes
if party:getChampion(1):getClass() == "Ranger" then
	toorumMode = 2
else
	toorumMode = 1
end

spawn("timer", self.level, self.x, self.y, 0, "no_throw_timer")
	:setTimerInterval(0.01)
	:addConnector("activate", "no_throw_timer", "deactivate")
	:addConnector("activate", "no_throw_script", "resetItem")
itemToDelete = nil
blockers = {}
movementBlockers = {}

function preventThrows()
	throwingAllowed = false
	spawnBlockers(party.x, party.y)
end

function allowThrows()
	throwingAllowed = true
	for i=1,#movementBlockers do
		movementBlockers[i]:destroy()
	end
	movementBlockers = {}
	for i=1,#blockers do
		blockers[i]:destroy()
	end
	blockers = {}
end

function setItem(source, item)
	itemToDelete = item
end

function resetItem()
	if itemToDelete == nil then
		hudPrint("Broken?")
		return
	end
	local name = itemToDelete.name
	itemToDelete:destroy()
	setMouseItem(spawn(name))
end

function moveBlockers(source, dir)
	if throwingAllowed then
		return
	end
	if findEntity("remove_no_throw_alcoves_timer") == nil then
		spawn("timer", party.level, party.x, party.y, 0, "remove_no_throw_alcoves_timer")
			:addConnector("activate", self.id, "removeNoThrowAlcoves")
			:setTimerInterval(0.5 / toorumMode)
			:activate()
	else
		remove_no_throw_alcoves_timer
			:setTimerInterval(0.5 / toorumMode)
			:activate()
	end
	local dx, dy = getForward(dir)
	spawnBlockers(party.x + dx, party.y + dy)
end

function turnBlockers()
	if throwingAllowed then
		return
	end
	if findEntity("remove_no_throw_alcoves_timer") == nil then
		spawn("timer", party.level, party.x, party.y, 0, "remove_no_throw_alcoves_timer")
			:addConnector("activate", self.id, "removeNoThrowAlcoves")
			:setTimerInterval(0.2 / toorumMode)
			:activate()
	else
		remove_no_throw_alcoves_timer
			:setTimerInterval(0.2 / toorumMode)
			:activate()
	end
	spawnMovementBlockers(party.x, party.y)
end

function removeNoThrowAlcoves()
	remove_no_throw_alcoves_timer:destroy()
	for i=1,#movementBlockers do
		movementBlockers[i]:destroy()
	end
	movementBlockers = {}
	
	local importantBlockers = {}	-- does not delete if player didn't actually move away (blocked by wall or something) --
	for i=1,#blockers-4 do
		if blockers[i].x == party.x and blockers[i].y == party.y then
			importantBlockers[#importantBlockers] = blockers[i]
		else
			blockers[i]:destroy()
		end
	end
	blockers = {blockers[#blockers-3], blockers[#blockers-2], blockers[#blockers-1], blockers[#blockers]}
	for i=1,#importantBlockers do
		blockers[#blockers] = importantBlockers[i]
	end
end

function spawnBlockers(x, y)
	-- prevent spawns from moving in the direction of off the map --
	if x < 0 then x = 0 end
	if y < 0 then y = 0 end
	if x > 31 then x = 31 end
	if y > 31 then y = 31 end
	blockers[#blockers+1] = spawn("no_throw_alcove_Y", party.level, x, y, 0)
	blockers[#blockers+1] = spawn("no_throw_alcove_X", party.level, x, y, 1)
	blockers[#blockers+1] = spawn("no_throw_alcove_Y", party.level, x, y, 2)
	blockers[#blockers+1] = spawn("no_throw_alcove_X", party.level, x, y, 3)
	spawnMovementBlockers(x, y)
end

function spawnMovementBlockers(x, y)
	movementBlockers[#movementBlockers+1] = spawn("no_throw_movement_alcove_Y", party.level, x, y, 0)
	movementBlockers[#movementBlockers+1] = spawn("no_throw_movement_alcove_X", party.level, x, y, 1)
	movementBlockers[#movementBlockers+1] = spawn("no_throw_movement_alcove_Y", party.level, x, y, 2)
	movementBlockers[#movementBlockers+1] = spawn("no_throw_movement_alcove_X", party.level, x, y, 3)
end
If you want to use the code as-is you should filter out throw attempt blocks for items with properties which would be lost if they were simply destroyed and respawned. Or get that part working. Feel free to customize/expand it to your needs. It's just currently impractical for me to get this method working in all cases at once.

Cheers!
Open Project -> Community FrankenDungeon: viewtopic.php?f=14&t=4276
User avatar
Komag
Posts: 3659
Joined: Sat Jul 28, 2012 4:55 pm
Location: Boston, USA

Re: anything other than a wall to prevent player throwing st

Post by Komag »

OH-MY-GOSH :o

You are one of the scripting gods around here!

I'll have to look into this more. It's for my initial insert-items-into-the-cube recreation, and thus I have only four grounded spots where I want this no-throw, so it might be simpler than your setup. But those spots also have specific unique socket alcoves in place that I can't mess up.

Would it work any differently to try to utilize keys/locks?

Anyway, thanks a ton! :D This shows that something is at least possible, if a little complex. I THINK I'm up to being able to handle what you've created, adapting it.
Finished Dungeons - complete mods to play
User avatar
Edsploration
Posts: 104
Joined: Wed Sep 19, 2012 4:32 pm

Re: anything other than a wall to prevent player throwing st

Post by Edsploration »

You're making something like the cube activation puzzle? Keys/locks could work, but I'd try alcoves first because with those you can see the object appear in the machine. They probably run off the exact same sensitive-clickable-volume method anyway.

But doesn't this puzzle involve placing objects into the right spots on a wall? Or can objects be thrown through the wall? I'm not really sure what you're dealing with.

I think I had a similar problem when I write the Big Friendly Warden in that my dragon statues, Warden, and wooden breakables were all permeable by thrown objects which got stuck in places the player couldn't reach. So I wrote a function that ran on a 1-second timer to destroy/respawn any stuck item. The only item property save I bothered with was to rewrite the text of the final scroll, but other scrolls/torches wouldn't be respawned properly.

Here's the script for reference. I apologize, It's fairly dirty code; I don't want to post it, but it might be useful.
SpoilerShow
  • loc -- Potential stuck item locations
  • a -- index for looping through the list of loc.
  • flags -- A series of flags set as each dragon statue is moved to turn on and off logic for the relevant spaces. This is filthy code but I was impatient. It's best to ignore.
  • Direction to respawn the item in was handled in the if statements, but it should have been labeled in the loc table or something cleaner.

Code: Select all

loc = {{x=17, y=17}, {x=17, y=18}, {x=23, y=16}, {x=23, y=17}, {x=23, y=18}, {x=23, y=19}, {x=22, y=16}, {x=15, y=17}, {x=13, y=19}, {x=14, y=19}}
flags = {false, false, false, false, false, false}

function moveItemCheck()
	local items = {"wand_fireball", "wand_movement", "final_scroll", "peasant_tunic", "loincloth", "diviner_cloak", "conjurers_hat",
					"huntsman_cloak", "round_key", "peasant_cap", "leather_boots", "peasant_breeches", "bone_amulet", "blue_gem",
					"torch", "silk_hose", "pointy_shoes", "sandals", "doublet", "nomad_boots", "tattered_cloak", "nomad_mittens",
					"baked_maggot", "grim_cap", "pitroot_bread", "snail_slice", "mole_jerky", "boiled_beetle", "rat_shank",
					"herder_cap", "blueberry_pie", "ice_lizard_steak", "rotten_pitroot_bread", "torch_everburning", "round_key",
					"gear_key", "ornate_key", "sack"}
	local checker = false
	local tempName
	local tempItem
	
	for a=1,#loc do
		if (a==1 and flags[1]==true) or
			(a==2 and flags[1]==false) or
			(a==3 and flags[2]==false) or
			(a==4 and flags[2]==true) or
			(a==5 and (flags[3]==false or flags[4]==true)) or
			(a==6 and flags[3]==true) or
			(a==7 and flags[2]==false) or
			(a==8) or
			(a==9 and flags[5]==false) or
			(a==10 and flags[6]==false) then
			
			for e in entitiesAt(self.level, loc[a].x, loc[a].y) do
				checker = false
				for i=1,#items do
					if (a~=10 or items[i]~="blue_gem") then
						if e.name == items[i] then
							checker = true
						end
					end
				end
				if checker == true then
					tempName = e.name
					e:destroy()
					if a == 1 or a == 2 then
						tempItem = spawn(tempName, self.level, loc[a].x-1, loc[a].y, 1)
					elseif a >= 3 and a <= 6 then
						tempItem = spawn(tempName, self.level, loc[a].x+1, loc[a].y, 3)
					elseif a == 7 then
						tempItem = spawn(tempName, self.level, loc[a].x+2, loc[a].y, 3)
					elseif a == 8 then
						if tempName == "final_scroll" then
							speech:talk()
							return
						else
							tempItem = spawn(tempName, self.level, loc[a].x, loc[a].y+1, 0)
						end
					elseif a == 9 or a == 10 then
						tempItem = spawn(tempName, self.level, loc[a].x, loc[a].y-1, 2)
					end
					if tempName == "final_scroll" then
						tempItem:setScrollText("Dearest BFW,\n\nI will be leaving you soon, but my love for you will last\nas long as your stone body. Please care for our home and\ncontinue to entertain guests. Stay true to your namesake.\nA stressed stone will turn to gravel.\n\nI have opened a passage behind the wall you tend to\npound on. In an emergency, please escape through it.\n\nLive on fair golem, and I will always be in your heart.\n\nLove,\n\n-Nana")
					end
				end
			end
		end
	end
end

function dragonFlag1()
	flags[1] = true
end

function dragonFlag2()
	flags[2] = true
end

function dragonFlag3()
	flags[3] = true
end

function dragonFlag4()
	flags[4] = true
end

function dragonFlag5()
	flags[5] = true
end

function dragonFlag6()
	flags[6] = true
end
Open Project -> Community FrankenDungeon: viewtopic.php?f=14&t=4276
User avatar
Komag
Posts: 3659
Joined: Sat Jul 28, 2012 4:55 pm
Location: Boston, USA

Re: anything other than a wall to prevent player throwing st

Post by Komag »

In the original game you have to place the correct parts on the correct sides of the sleeping cube. For instance, on one side you stick the ore into a round hole. In the original, if you click the ore off to the left of the hole, nothing happens, it does not get thrown. If you click it in the middle over the hole, it goes in.

In my recreation setup, I indeed used socket/alcoves, but the problem is that if I click the ore off to the left of the hole, it gets thrown, makes a big clank noise and drops, and once in a while it lands sort of inside the barrier wall model used in those locations before the cube breaks free and it hard to pick back up.
Finished Dungeons - complete mods to play
User avatar
Edsploration
Posts: 104
Joined: Wed Sep 19, 2012 4:32 pm

Re: anything other than a wall to prevent player throwing st

Post by Edsploration »

It's probably best to just stretch a large alcove over each surface of the cube barrier model at a deeper depth than the alcoves sensitive to placing the items in them. Use this backdrop alcove's onInsertItem hook to tie this to a simple item destroy/respawn-on-mouse-pointer script which is called from a 0.01 s timer.

It's essentially the same as my first post except the alcoves are static and stick on the sleeping cube rather than following the player around dynamically.

A tricky thing would be reduplicating the killing the cube puzzle. It could be done with alcoves that are placed around the cube when it is stunned. I've theorized that killing a cube in script is possible in a hacky way by spawning an invisible cube on top of it, and then dropping that one off the map with an invisible pit. The problem is the invisible cube will only crush the visible one if it moves onto it, and not if it's simply spawned on top. The cube ignores props so you can't even control its movement with invisible walls. But perhaps you could spawn it one floor above, spawn a pit there, and have it fall onto the visible cube? Hmmmm....

I'm probably far too willing to go down these hacky scripting paths. Maybe with more functionality in coming patches AH can save me from myself. :lol:
Open Project -> Community FrankenDungeon: viewtopic.php?f=14&t=4276
User avatar
Komag
Posts: 3659
Joined: Sat Jul 28, 2012 4:55 pm
Location: Boston, USA

Re: anything other than a wall to prevent player throwing st

Post by Komag »

Oh I'm already done with the full cube fight recreation - I use a complex method of constantly tracking the cube's rotational position (there are 24 possible positions for a cube - one of six sides up and four rotations for each side) based on it's movement, then spawn the precise socket alcoves when the cube is frozen (teleport the cube away and spawn the correct one-of-six fake ones in its place), then rig up all the right messages and sounds and stuff, and use the real cube's activity as much as possible so I don't have to rig and fake and hack as much. It's crazy but it works pretty well overall 8-)

Anyway, I'll try the alcove approach you mentioned, putting a large one "deeper" in, see if that works for me, thanks
Finished Dungeons - complete mods to play
User avatar
Komag
Posts: 3659
Joined: Sat Jul 28, 2012 4:55 pm
Location: Boston, USA

Re: anything other than a wall to prevent player throwing st

Post by Komag »

Edsploration wrote: STILL NEEDED: Proper item save/respawn logic keeping track of scroll text or any item with properties that must be set after it is spawned. I've encountered the need for this twice now so I may get around to writing this logic tree someday.
I solved most of this here:

Code: Select all

wasHolding = 0
wasHoldingName = 0
wasHoldingID = 0
wasHoldingStack = 0
wasHoldingText = 0
wasHoldingFuel = 0
wasHoldingCharges = 0

function mouseSave() -- triggered by dreamStart(), and by dreamWakeScript.dreamStart()
  print("running mouseSave")
  if getMouseItem() ~= nil then
     print("found an item in mouse, called "..getMouseItem().name)
     wasHolding = 1
     wasHoldingName = getMouseItem().name
     wasHoldingID = getMouseItem().id
     wasHoldingStack = getMouseItem():getStackSize()
     print("mouse item stack size "..wasHoldingStack)
     if wasHoldingName == "scroll" or wasHoldingName == "note" then
        wasHoldingText = getMouseItem():getScrollText()
        print(""..wasHoldingText)
     end
     wasHoldingFuel = getMouseItem():getFuel()
     print("mouse item fuel "..wasHoldingFuel)
     wasHoldingCharges = getMouseItem():getCharges()
     print("mouse item charges "..wasHoldingCharges)
     setMouseItem(nil)
  end
end

function mouseRestore() -- triggered by dreamReturn
  if wasHolding == 1 then
     setMouseItem(spawn(""..wasHoldingName))
     print("returned to mouse them item "..getMouseItem().name)
     if wasHoldingStack > 0 then
        getMouseItem():setStackSize(wasHoldingStack)
        print("restoring stack size...")
     end
     if wasHoldingName == "scroll" or wasHoldingName == "note" then
        getMouseItem():setScrollText(wasHoldingText)
        print("restoring text...")
     end
     getMouseItem():setFuel(wasHoldingFuel)
     print("restoring fuel, if any...")
     if wasHoldingCharges > 0 then
        getMouseItem():setCharges(wasHoldingCharges)
        print("restoring charges...")
     end
     wasHolding = 0
  end
end
Still would have to add support for the biggie, containers and their contents (and all their individual properties!!!).

There also seems to be no such command as item:getScrollImage(), so any special notes with maps or something would have to be defined as unique objects in order for them to be properly restored based on their item.name, with a unique one for each one, then you can restore the proper image with setScrollImage
Finished Dungeons - complete mods to play
User avatar
Xanathar
Posts: 629
Joined: Sun Apr 15, 2012 10:19 am
Location: Torino, Italy
Contact:

Re: anything other than a wall to prevent player throwing st

Post by Xanathar »

STILL NEEDED: Proper item save/respawn logic keeping track of scroll text or any item with properties that must be set after it is spawned. I've encountered the need for this twice now so I may get around to writing this logic tree someday.
You can find this logic either in this post: viewtopic.php?f=14&t=3099&p=42098&hilit ... ory#p42098
Or at the bottom of the script of the GrimQ Library: viewtopic.php?f=14&t=4256

It has two methods loadItem and saveItem.

saveItem saves an item and returns a table (compatible with savegames). loadItem restores a table into an item.
This way you have only one variable to keep an item serialized, and you can even put the item tables in a greater table to keep all an entire inventory (see post#1 I linked).
It supports containers (albeit it loses how the item were placed into the container) and everything except scroll images.

They are tested enough (thanks to Komag himself for that!) and used also in the One Room dungeon) so feel free to use them as you wish - the only thing they don't support as I said is getScrollImage, but it was one of my requests for petri when he went into his coding binge the other day :lol: and so it should be in the next version (thanks Petri!).

Edit:
As a difference from Komag script: I don't keep the id saved - if you need to save and restore it for any reason it must be added (in theory I prefer not to, and I never had the need, but I understand other mods may rely on the ids being preserved).

Edit#2:
I will put a version of GrimQ later today or more likely tomorrow with also : copyItem, moveToContainer and moveItemsFromTileToAlcove - if anyone interested.
Waking Violet (Steam, PS4, PSVita, Switch) : http://www.wakingviolet.com

The Sunset Gate [MOD]: viewtopic.php?f=14&t=5563

My preciousss: http://www.moonsharp.org
Post Reply