[UMod] Editor+ (minor editor improvements)

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
7Soul
Posts: 209
Joined: Sun Oct 19, 2014 1:56 am
Location: Brazil

[UMod] Editor+ (minor editor improvements)

Post by 7Soul »

This mod adds a few minor changes to the editor:

- Displays map number in map list
- Ability to use up/down arrows to reorder maps
- Ability to filter objects by their traits
- Key shortcuts to restart the editor (Ctrl+Y) and go back to menu (Ctrl+T)
- Makes map list a little bigger by default
- Connector arrows now work with objects that use "pillar" placement
- Created objects are automatically placed at the elevation of the tile they're on
- Fixes a number of crashes
- The 'tile picker' hotkeys now work in heightmap mode
- Map Layout viewer: preview the positioning of your maps
- Tile Selection tool: copy, paste, rotate and flip sections of tiles

Image

Download: https://www.nexusmods.com/legendofgrimr ... escription

How to use:

1 - First you have to be on the new beta branch. On steam, right click the game > Properties > Betas. Add the code "ggllooeegggg" to unlock the secret "nutcracker" beta

2 - Go to "\Documents\Almost Human\legend of grimrock 2". Once the beta is downloaded, you'll see a file named "mods.cfg" and a "Mods" folder

3 - In the Mods folder, create a text file and paste the code from the end of this post, name it "editorPlus.lua" (or any name you want). Confirm when windows ask if you want to change the extension

4 - Add the mod to mods.cfg so it looks like this:

Code: Select all

mods = {
	"editorPlus.lua",
}
Last edited by 7Soul on Sun Jun 09, 2024 4:46 pm, edited 6 times in total.
Join the LoG discord server: https://discord.gg/ArgAgNN :D

My Mods
kelly1111
Posts: 349
Joined: Sun Jan 20, 2013 6:28 pm

Re: [UMod] Editor+ (minor editor improvements)

Post by kelly1111 »

Very nice. thank you.
kelly1111
Posts: 349
Joined: Sun Jan 20, 2013 6:28 pm

Re: [UMod] Editor+ (minor editor improvements)

Post by kelly1111 »

I have noticed something odd with the editor after installing the mod. When I want to delete an object with the del key that is on top of other objects (stacked), it wont let me delete it. It does however let me delete single objects.
User avatar
7Soul
Posts: 209
Joined: Sun Oct 19, 2014 1:56 am
Location: Brazil

Re: [UMod] Editor+ (minor editor improvements)

Post by 7Soul »

kelly1111 wrote: Fri Jan 22, 2021 11:24 am I have noticed something odd with the editor after installing the mod. When I want to delete an object with the del key that is on top of other objects (stacked), it wont let me delete it. It does however let me delete single objects.
Updated in the first post
Join the LoG discord server: https://discord.gg/ArgAgNN :D

My Mods
Killcannon
Posts: 73
Joined: Sun Apr 12, 2015 2:57 pm

Re: [UMod] Editor+ (minor editor improvements)

Post by Killcannon »

Question, how do we get the Beta on GoG, and other non-steam related platforms?
User avatar
7Soul
Posts: 209
Joined: Sun Oct 19, 2014 1:56 am
Location: Brazil

Re: [UMod] Editor+ (minor editor improvements)

Post by 7Soul »

Killcannon wrote: Wed Jun 30, 2021 1:12 am Question, how do we get the Beta on GoG, and other non-steam related platforms?
I don't think it's possible yet :/
Join the LoG discord server: https://discord.gg/ArgAgNN :D

My Mods
User avatar
7Soul
Posts: 209
Joined: Sun Oct 19, 2014 1:56 am
Location: Brazil

Re: [UMod] Editor+ (minor editor improvements)

Post by 7Soul »

Small update 0.2.0 (copy code in first post):

- Connector arrows now work with objects that use "pillar" placement
Image
Join the LoG discord server: https://discord.gg/ArgAgNN :D

My Mods
User avatar
Isaac
Posts: 3185
Joined: Fri Mar 02, 2012 10:02 pm

Re: [UMod] Editor+ (minor editor improvements)

Post by Isaac »

That's really cool. 8-)

I wish Petri had updated the GoG and Direct sales versions before abandoning the business. :(
(I do not have the Steam version, and it irks that anyone who would play a Umod has to have the Steam version to do it.)
User avatar
7Soul
Posts: 209
Joined: Sun Oct 19, 2014 1:56 am
Location: Brazil

Re: [UMod] Editor+ (minor editor improvements)

Post by 7Soul »

Update 0.3.0

- Created objects now take on the elevation of the tile they're placed on
- Fixes a crash related with copy-pasting wall objects over other walls
- Fixes a crash related to placing pillars at the edge of maps

Code: Select all

-- This file contains sections of Legend of Grimrock 2 source code; anything you
-- do with this file must comply with the Grimrock modding terms:
-- http://www.grimrock.net/modding_log1/modding-and-asset-usage-terms/
--
-- You are free to alter this mod or reuse its code in other Grimrock mods.

--[=[
Made by 7Soul (henriquelazarini@gmail.com)]]

version = "0.3.0"
]=]

DungeonEditor.backToMenuKey = "T" -- ctrl + this key goes back to main menu
DungeonEditor.fullRestart = "Y"   -- ctrl + this key restarts the editor and reloads mods

-------------------------------------------------------------------------------------------------------
-- Editor Functions                                                                                  --    
-------------------------------------------------------------------------------------------------------

local oldDungeonInit = DungeonEditor.init
function DungeonEditor:init()
	oldDungeonInit(self)
	self.splitter1 = 252
	self.splitter2 = 476
	self.splitter3 = 340
	self.splitter4 = 200 + 310 -- larger map list
	self.minHeight = -6
	self.maxHeight = 6
end

function DungeonEditor:update()
	if not renderer:isReadyToRender() or (config.sleepWhenNoFocus and not mainFrame:hasFocus()) then
		sys.sleep(100)
		return
	end

	if self.pendingLoadDungeon then
		self:loadDungeon(self.pendingLoadDungeon)
		self.pendingLoadDungeon = nil
	end

	updateTime()
	updateFileChangeRequests()
	
	--sys.sleep(50)
	
	-- update input state
	local windowSizeChanged = false
	do
		local state = imgui.state
		state.doubleClick = detectDoubleClick()

		-- clear unprocessed keys
		state.keyInput = {}
		state.mouseWheel = 0
		
		-- poll events
		while true do
			local event = mainFrame:pollEvents()
			if not event then break end
			if event.type == "key" and event.down then
				--print(event.key, event.char)			
				state.keyInput[#state.keyInput+1] = event
				
				-- global keys
				local action = config:convertEditorKeyToAction(event.keyCode, event.modifiers)
				if event.key == "O" and event.modifiers == 2 then self:onOpenProject() end
				if event.key == "S" and event.modifiers == 2 then self:onSaveProject() end
				if event.key == "R" and event.modifiers == 2 then self:onReloadProject() end
				if event.key == DungeonEditor.backToMenuKey and event.modifiers == 2 then self:onBackToGame() end
				if event.key == DungeonEditor.fullRestart and event.modifiers == 2 then sys.restart{ "launchEditor" } end
				if action == "start_preview" then self:playPreview() end
				if action == "stop_preview" then self:stopPreview() end
			elseif event.type == "mouse_wheel" then
				state.mouseWheel = state.mouseWheel + event.delta
			elseif event.type == "menu" then
				self:onMenuEvent(event.id)
			elseif event.type == "resize" then
				self.windowWidth = event.width	-- 0 when window is minimized
				self.windowHeight = event.height
				windowSizeChanged = true
			elseif event.type == "close" then
				self:confirmClose(function() sys.exit() end)
			end
		end		
	end

	-- early out if window is minimized
	if self.windowWidth == 0 or self.windowHeight == 0 then return end

	-- recreate render window if window was resized
	if windowSizeChanged then
		renderer:resizeRenderBuffers(self.windowWidth, self.windowHeight)
	end
		
	-- update steam
	steamContext:update()
		
	renderer:setViewport(0, 0, self.windowWidth, self.windowHeight)
	renderer:beginRender()
	
	ImmediateMode.beginDraw()
	imgui.prepare(mainFrame)

	local screenWidth = self.windowWidth
	local screenHeight = self.windowHeight

	if self.previewMode and self.fullscreen then
		-- fullscreen preview mode
		self:preview(0, 0, screenWidth, screenHeight)
	else
		ImmediateMode.fillRect(0, 0, screenWidth, screenHeight, {41,41,41,255})
		
		self:toolBar(20, 10, 200, 30)
		self:brushInfo(self.splitter1 + 2, 10, (screenWidth - self.splitter1) - self.splitter2 - 4, 30)

		-- screen height without status bar
		local screenHeight2 = screenHeight - 20
		
		-- project/asset browser splitter
		do
			local x = 0
			local y = 44
			local width = self.splitter1 - 2
			local height = screenHeight2 - y
			
			-- asset browser
			do
				local y = self.splitter4+2
				local height = screenHeight2 - y
				self:assetBrowser(x, y, width, height)
			end
			
			-- project explorer
			do
				local height = self.splitter4 - y - 2
				self:projectExplorer(x, y, width, height)
			end

			-- splitter 4
			self.splitter4 = imgui.vsplitter("splitter4", x, self.splitter4, width)
			self.splitter4 = math.clamp(self.splitter4, y + 50, screenHeight2 - 22)
		end
		
		-- map view
		do
			local x = self.splitter1 + 4
			local y = 44
			local width = (screenWidth - self.splitter2) - self.splitter1 - 8
			local height = screenHeight2 - y
			self:mapView(x, y, width, height)
		end
		
		-- preview/inspector splitter
		do
			local x = (screenWidth - self.splitter2) + 2
			local y = 44
			local width = screenWidth - x
			local height = screenHeight2 - y
			
			-- inspectors
			do
				local y = self.splitter3+2
				local height = screenHeight2 - y
				local sel = iff(#self.selection == 1, self.selection[1], nil)							
				self.inspector:inspect(sel, x, y, width, height, 0)
			end
				
			-- preview
			do
				local height = self.splitter3 - y - 2
				self:previewButtons(x, 10)
				if self.previewMode and self.fullscreen then
					self:preview(0, 0, screenWidth, screenHeight)
				else
					self:preview(x, y, width, height)
				end
			end

			-- splitter 3
			self.splitter3 = imgui.vsplitter("splitter3", x, self.splitter3, width)
			self.splitter3 = math.clamp(self.splitter3, y + 50, screenHeight2 - 22)
		end
				
		-- status bar
		do
			local h = 17
			local x = 0
			local y = screenHeight - h
			ImmediateMode.fillRect(x, y, screenWidth, h, {56,56,56,255})
			if self.status then ImmediateMode.drawText(self.status, x+4, y+2, imgui.state.font, {200,200,200,255}) end
			
			-- draw coordinates
			if self.mouseCellX and self.mouseCellY then
				local text = string.format("%d,%d", self.mouseCellX, self.mouseCellY)
				local textWidth = imgui.state.font:getTextWidth(text)
				local x = x + screenWidth - textWidth - 4
				ImmediateMode.fillRect(x-4, y, textWidth+8, h, {56,56,56,255})
				ImmediateMode.drawText(text, x, y + 2, imgui.state.font, {200,200,200,255})
			end
		end
		
		-- splitter 1 (measured from left screen edge)
		self.splitter1 = imgui.hsplitter("splitter1", self.splitter1, 0, screenHeight)
		self.splitter1 = math.clamp(self.splitter1, 10, (screenWidth - self.splitter2)-10)
		
		-- splitter 2 (measured from right screen edge)
		self.splitter2 = screenWidth - imgui.hsplitter("splitter2", screenWidth - self.splitter2, 0, screenHeight)
		self.splitter2 = math.clamp(self.splitter2, 10, (screenWidth - self.splitter1)-10)

		self:updateContextMenu()
	end
	
	if self.dialog then
		self.dialog:update()
		if self.dialog.close then self.dialog = nil end
	end

	imgui.finish()
	
	ImmediateMode.endDraw()

	renderer:endRender()

	tvec.free()
	tmat.free()
end

-- Project explorer

function DungeonEditor:projectExplorer(x, y, width, height)
	x,y,width,height = self:panel("Project", x, y, width, height)	

	if not dungeon then return end
	
	imgui.beginArea(x, y, width, height)

	ImmediateMode.drawRect(x, y, x+width-1, y+height-1, {50,50,50,255})
	
	local lineHeight = 13
	local scroll = self.projectScroll
	
	do
		local y = y
		for i=1,#dungeon.maps do
			local map = dungeon.maps[i]
			
			-- level visibility
			local oldState = map._levelDisabled
			if oldState == nil then oldState = false end
			map._levelDisabled = not self:levelTick("level_check_"..i, x, y - scroll, 16, lineHeight, not map._levelDisabled)
			
			-- special case: control click hides all other levels
			if oldState ~= map._levelDisabled and sys.keyDown("control") then
				for j=1,#dungeon.maps do
					local map = dungeon.maps[j]
					map._levelDisabled = (i ~= j)
				end
				self.map = dungeon.maps[i]
			end

			-- level coord and name
			local x = x + 20
			local lx,ly,lz = map:getLevelCoord()
			local item = string.format("%02d (%d,%d,%d) %s", i, lx, ly, lz, map.name) -- show map id
			if map == self.map then
				ImmediateMode.fillRect(x, y - scroll, width, lineHeight, {48,72,96,255})
				local color = iff(map._levelDisabled, {200,200,200,255}, Color.White)
				ImmediateMode.drawText(item, x, y - scroll, imgui.state.font, color)
			else
				local color = iff(map._levelDisabled, {115,115,115,255}, {206,206,206,255})
				ImmediateMode.drawText(item, x, y - scroll, imgui.state.font, color)
			end
			y = y + lineHeight
		end
	end
	
	imgui.endArea()

	if imgui.buttonLogic("project_explorer", x + 14, y, width - 14, height) then
		local y = math.floor((imgui.state.mouseY - y + scroll) / lineHeight) + 1
		if y >= 1 and y <= #dungeon.maps then
			self.map = dungeon.maps[y]
		end
	end
	
	-- context menu
	if imgui.state.hot == "project_explorer" and sys.mousePressed(2) then
		local state = imgui.state
		local y = math.floor((imgui.state.mouseY - y + scroll) / lineHeight) + 1
		if y >= 1 and y <= #dungeon.maps then
			self.map = dungeon.maps[y]
			self:contextMenu{
				"New Level", function() self:newLevel() end,
				"Delete", function() self:deleteLevel() end,
				"Move Up", function() self:moveLevelUp() end,
				"Move Down", function() self:moveLevelDown() end,
				"Sort", function() self:sortLevels() end,
				"Properties", function() self.dialog = MapPropertiesDialog.create() end,
			}
		end
	end

	if imgui.state.hot == "project_explorer" and not self.dialog then
		while #imgui.state.keyInput > 0 do
			local ev = imgui.state.keyInput[1]
			table.remove(imgui.state.keyInput, 1)
		
			local action = config:convertEditorKeyToAction(ev.keyCode, ev.modifiers)
			
			if ev.key == "up" and ev.modifiers == 0 then self:moveLevelUp() end
			if ev.key == "down" and ev.modifiers == 0 then self:moveLevelDown() end
		end
	end

	-- scroll bar
	local numItems = #dungeon.maps
	self.projectScroll = imgui.vscrollbar("project_scroller", x+width-10, y, 10, height, self.projectScroll, height, numItems*lineHeight)
	
	-- mouse wheel scrolling
	if imgui.state.hot == "project_explorer" and imgui.state.mouseWheel ~= 0 then
		self.projectScroll = self.projectScroll - imgui.state.mouseWheel * lineHeight * 5
	end

	self.projectScroll = math.clamp(self.projectScroll, 0, math.max(numItems*lineHeight - height, 0))	
end

function DungeonEditor:assetBrowser(x, y, width, height)
	--ImmediateMode.fillRect(x, y, width, height, {50,50,50,255})

	x,y,width,height = self:panel("Asset Browser", x, y, width, height)
	x = x + 2
	y = y + 0
	width = width - 2
	height = height

	if not dungeon then return end
	
	ImmediateMode.pushState()
	ImmediateMode.clipTo(x, y, x+width, y+height)	

	-- search field
	do
		local w = math.min(120, width)
		self.findAsset = self:searchBox("asset_find", x, y+1, width, nil, self.findAsset)
		y = y + 20
		height = height - 20
	end
	
	-- collect set of tags from archs
	local tags = {}
	do
		local s = {}
		for _,a in pairs(dungeon.archs) do
			if a.editorIcon then
				for t,_ in pairs(a.tags) do
					s[t] = true
				end
			end
		end

		-- convert to list of tags
		for t,_ in pairs(s) do
			tags[#tags+1] = t
		end
		table.sort(tags)
		table.insert(tags, 1, "any")
	end

	-- collect set of traits from archs
	local traits = {}
	do
		local s = {}
		for _,a in pairs(dungeon.archs) do
			if a.editorIcon and a.components then
				for _,c in ipairs(a.components) do
					if c.traits then
						for _,t in pairs(c.traits) do
							s[t] = true
						end
					end
				end
			end
		end

		-- convert to list of traits
		for t,_ in pairs(s) do
			traits[#traits+1] = t
		end
		table.sort(traits)
		table.insert(traits, 1, "any")
	end
	
	-- filter
	if self.mode ~= "brush_tool" then
		y = y + 3
		imgui.label("Tags", x+2, y+3)
		self.assetFilter = imgui.combobox("asset_filter", x+45, y, 135, 18, self.assetFilter or 1, tags)
		y = y + 23
		height = height - 26

		y = y + 3
		imgui.label("Traits", x+2, y+3)
		self.assetFilter2 = imgui.combobox("asset_filter2", x+50, y, 135, 18, self.assetFilter2 or 1, traits)
		y = y + 23
		height = height - 26
	else
		local h = 3
		y = y + h
		height = height - h
	end
	
	-- list of assets
	do
		local height = height
		
		ImmediateMode.pushState()
		ImmediateMode.clipTo(x, y, x+width, y+height)	

		ImmediateMode.drawRect(x, y, x+width-1, y+height-1, {50,50,50,255})
		
		local filter = tags[self.assetFilter]
		local filter2 = traits[self.assetFilter2]
		
		local assets = {}
		
		if self.mode == "brush_tool" then
			-- tiles
			for _,tile in pairs(self.dungeon.tiles) do
				if not self.currentBrush[1] then self.currentBrush[1] = tile end
				if not self.currentBrush[2] then self.currentBrush[2] = tile end

				-- filter asset by name
				local ignore
				if #self.findAsset > 0 and not string.match(tile.name, self.findAsset, 1, true) then
					ignore = true
				end
		
				if not ignore then assets[#assets+1] = tile end
			end
		else
			-- archs
			for _,a in pairs(dungeon.archs) do
				if a.editorIcon and not string.match(a.name, "^base%_") then
					-- filter by tags
					local ignore
					if filter ~= "any" and not a.tags[filter] then 
						ignore = true
					end

					-- filter by traits
					if a.components then
						local traitComp
						for _,c in ipairs(a.components) do
							if c.traits then
								traitComp = c
							end
						end

						if traitComp then
							if filter2 ~= "any" and not table.contains(traitComp.traits, filter2) then
								ignore = true
							end
						else
							if filter2 ~= "any" then
								ignore = true
							end
						end
					end
					
					if a.name == "party" then ignore = true end
					
					-- filter asset by name
					if #self.findAsset > 0 and not string.find(a.name, self.findAsset, 1, true) then
						ignore = true
					end
					
					if not ignore then
						assets[#assets+1] = a
					end
				end
			end
		end
		
		-- sort alphabetically
		table.sort(assets, function(l,r) return l.name < r.name end)

		local lineHeight = 20
		
		do
			local y = y
			for i=1,#assets do
				local a = assets[i]
				
				local selected
				if self.mode == "brush_tool" then
					selected = (self.currentBrush[1] == a)
				else
					selected = (self.selectedAsset == a)
				end
				
				if self.currentBrush[2] == a then
					ImmediateMode.fillRect(x, y - self.assetScroll, width, lineHeight, {48,72,96,128})
				end
				
				if selected then
					ImmediateMode.fillRect(x, y - self.assetScroll, width, lineHeight, {48,72,96,255})
					ImmediateMode.drawText(a.name, x+22, y - self.assetScroll+4, imgui.state.font, Color.White)
				else
					ImmediateMode.drawText(a.name, x+22, y - self.assetScroll+4, imgui.state.font, {200,200,200,255})
				end

				if a.editorIcon then
					local y = y - self.assetScroll
					if a.editorIcon == 24 then y = y + 6 end
					self:drawMapTile(x, y, a.editorIcon, false, 0, a.color or Color.White)
				end

				y = y + lineHeight
			end
		end
		
		ImmediateMode.popState()

		if self.mode == "brush_tool" then
			-- imgui.buttonLogic() does not work with rmb...
			local hover = imgui.regionHit(x, y, width, height)
			if hover then
				local y = math.floor((imgui.state.mouseY - y + self.assetScroll) / lineHeight) + 1
				
				if imgui.state.hot == "asset_browser" then
					if sys.mousePressed(0) then self.currentBrush[1] = assets[y] end
					if sys.mousePressed(2) then self.currentBrush[2] = assets[y] end
				end

				imgui.state.newHot = "asset_browser"
			end
		else
			if imgui.buttonLogic("asset_browser", x, y, width, height) then
				local y = math.floor((imgui.state.mouseY - y + self.assetScroll) / lineHeight) + 1
				self.selectedAsset = assets[y]
				self.mode = "place_objects"
				self.status = "Add Object: Click on the map to add object"				
				imgui.state.focus = "asset_browser"
			end
		end
		
		-- scroll bar
		self.assetScroll = imgui.vscrollbar("asset_scroller", x+width-10, y, 10, height, self.assetScroll, height, #assets*lineHeight)
		
		-- mouse wheel scrolling
		if imgui.state.hot == "asset_browser" and imgui.state.mouseWheel ~= 0 then
			self.assetScroll = self.assetScroll - imgui.state.mouseWheel * lineHeight * 4
		end

		self.assetScroll = math.clamp(self.assetScroll, 0, math.max(#assets*lineHeight - height, 0))
	end

	ImmediateMode.popState()
end

function DungeonEditor:drawConnectors(x, y, ent, color)
	local s = 20
	
	for i=1,ent.components.length do
		local comp = ent.components[i]

		if comp.connectors then
			for _,connector in ipairs(comp.connectors) do
				local target = connector.target
				if target then
					target = self.map:findEntity(target)
					if target then
						local x1 = x + ent.x * s + s/2
						local y1 = y + ent.y * s + s/2
						if ent.arch.placement == "wall" then
							local dx,dy = getDxDy(ent.facing)
							x1 = x1 + dx*10
							y1 = y1 + dy*10
						elseif ent.arch.placement == "pillar" then
							x1 = x1 - 10
							y1 = y1 - 10
						end

						local x2 = x + target.x * s + s/2
						local y2 = y + target.y * s + s/2
						if target.arch.placement == "wall" then
							local dx,dy = getDxDy(target.facing)
							x2 = x2 + dx*10
							y2 = y2 + dy*10
						elseif target.arch.placement == "pillar" then
							x2 = x2 - 10
							y2 = y2 - 10
						end

						self:drawConnectorArrow(x1, y1, x2, y2, color)
					end
				end
			end
		end
		
		if comp.__class == TeleporterComponent or comp.__class == StairsComponent then
			local tlevel,tx,ty = comp:getTeleportTarget(comp)
			if tx and ty then
				local x1 = x + ent.x * s + s/2
				local y1 = y + ent.y * s + s/2
				local x2 = x + tx * s + s/2
				local y2 = y + ty * s + s/2
				self:drawConnectorArrow(x1, y1, x2, y2, color)
			end
		end
	end
end

function DungeonEditor:addObjectTool(x, y, width, height, pressed)
	local arch = self.selectedAsset
	local s = 20
	local map = self.map
	
	if not arch then return end
	
	-- mouse square
	local mx,my = self.mouseX,self.mouseY
	mx = (mx - x) / s
	my = (my - y) / s
	local fx,fy = mx % 1,my % 1
	mx = math.floor(mx)
	my = math.floor(my)
	
	if mx < 0 or my < 0 or mx >= map.width or my >= map.height then
		return
	end

	local facing = 0
	local valid = true
	local tx,ty
	
	if arch.placement == "pillar" then
		-- pillar
		facing = math.random(0,3)
		mx = math.floor((self.mouseX - x) / s + 0.5)
		my = math.floor((self.mouseY - y) / s + 0.5)
		tx = x + mx * s - 2
		ty = y + my * s - 2
		
		-- snap to wall if holding shift
		if sys.keyDown("shift") then
			valid = not map:isWall(mx-1, my-1) or not map:isWall(mx-1, my) or not map:isWall(mx, my-1) or not map:isWall(mx, my)
		end
	elseif arch.placement == "wall" then
		facing = nil
		
		-- snap to wall if holding shift
		if sys.keyDown("shift") then
			local dist = math.huge
			if map:isWall(mx, my) then
				if not map:isWall(mx, my-1) then
					local d = fy
					if d < dist then facing = 0; dist = d end
				end
				if not map:isWall(mx+1, my) then
					local d = 1 - fx
					if d < dist then facing = 1; dist = d end
				end
				if not map:isWall(mx, my+1) then
					local d = 1 - fy
					if d < dist then facing = 2; dist = d end
				end
				if not map:isWall(mx-1, my) then
					local d = fx
					if d < dist then facing = 3; dist = d end
				end
				if facing then
					local dx,dy = getDxDy(facing)
					mx = mx + dx
					my = my + dy
					facing = (facing + 2) % 4
				end
			else
				if map:isWall(mx, my-1) then
					local d = fy
					if d < dist then facing = 0; dist = d end
				end
				if map:isWall(mx+1, my) then
					local d = 1 - fx
					if d < dist then facing = 1; dist = d end
				end
				if map:isWall(mx, my+1) then
					local d = 1 - fy
					if d < dist then facing = 2; dist = d end
				end
				if map:isWall(mx-1, my) then
					local d = fx
					if d < dist then facing = 3; dist = d end
				end
			end
			valid = (facing ~= nil)
		end
		
		if not facing then facing = getQuadrant(fx, fy) end
		
		local dx,dy = getDxDy(facing)
		tx = x + mx * s + dx*10
		ty = y + my * s + dy*10
	elseif arch.placement == "floor" or arch.placement == "ceiling" then
		-- floor item
		facing = getQuadrant(fx, fy)
		tx = x + mx * s
		ty = y + my * s
		
		-- snap to empty tile if holding shift
		if sys.keyDown("shift") then
			valid = not map:isWall(mx, my)
		end
	else
		facing = getQuadrant(fx, fy)
		tx = x + mx * s
		ty = y + my * s
	end
	
	self:drawMapTile(tx, ty, arch.editorIcon + facing, false, 0, iff(valid, Color.White, {255,255,255,64}))

	if pressed and valid then
		local name = self.selectedAsset.name
		-- Prevents crash from placing pillars at the edge of the map
		if mx < 0 or my < 0 or mx >= map.width or my >= map.height then
			systemLog:write("[Warn] Attempted to insert map object " .. name .. " outside map boundaries")
			return
		end
		
		-- replace existing starting location
		if name == "starting_location" then
			self:setStartingLocationTo(self.map.level, mx, my, facing)
		else
			local id_ = self:generateUniqueId(name)
			-- Created objects now take on the elevation of the tile they're placed on
			local elevation = self.map:getElevation(mx, my)
			spawn(self.map, name, mx, my, facing, elevation, id_, false)
		end
		
		self:modify()
	end
end

-- Prevents a bug when copy-pasting objects (such as walls) that destroys other walls
local oldMapRemoveWall = Map.removeWall
function Map:removeWall(x, y, facing, elevation)
	if self.dungeon.editorDungeon then return end
	oldMapRemoveWall(self, x, y, facing, elevation)
end
Join the LoG discord server: https://discord.gg/ArgAgNN :D

My Mods
User avatar
The_Morlock
Posts: 25
Joined: Sun Jun 02, 2024 6:15 am

Re: [UMod] Editor+ (minor editor improvements)

Post by The_Morlock »

Great job on this... wished I could use it! HA!

So, using the Steam version here and set the BETA and the key. Still haven't seen the Mods folder nor the mods.cfg file. Version still shows v2.2.4.

By chance can this just be created? Mind sharing the mods.cfg file so I can make it myself?

Thanks and great job!

Quick question, if there a way to add custom filters? i.e. walls, furniture (alters and such), etc.
Last edited by The_Morlock on Sun Jun 09, 2024 7:19 am, edited 1 time in total.
Post Reply