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):
- Item properties are not preserved. Scroll text is lost, charges are lost, torch duration is reset, etc.
- 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.
- Impossible to place an item on altars. Fixing this would take splitting each alcove into 3 for left, right, and deep center behind altar.
- May conflict with other custom alcoves. Pretty much must be dealt with on a case-by-case basis.
- 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!

Throw-blocking which can be turned on and off by calling the preventThrows() and allowThrows() functions!

Timing adjustments for Toorum! (untested... but he's exactly 2 times as fast, right?)

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!