#include "script/common.lua" #include "ui/ui_extensions.lua" #include "ui/ui_helpers.lua" #include "ui/components/buttons.lua" VOXEL_SIZE = 0.1 MAX_DIST = 24 function squareDistance(a0, a1, b0, b1) local u = VecSub(a1, a0) local v = VecSub(b1, b0) local w = VecSub(a0, b0) local a = VecDot(u, u) local b = VecDot(u, v) local c = VecDot(v, v) local d = VecDot(u, w) local e = VecDot(v, w) local D = a*c - b*b local sc, tc; if D < 0.000001 then sc = 0.0 if b > c then tc = d / b else tc = e / c end else sc = (b*e - c*d) / D; tc = (a*e - b*d) / D; end return sc, tc end --Return point on (p0, d0) that is closest to (p1, d1) function getClosestPointRayRay(p0, d0, p1, d1) local a, _ = squareDistance(p0, VecAdd(p0, d0), p1, VecAdd(p1, d1)) return VecAdd(p0, VecScale(d0, a)) end function math.round(v) return math.floor(v+0.5) end active = nil drawShape = nil drawPoint = nil drawStartPos = nil drawNormal = nil canDraw = nil pickShape = nil gGamepadAlignToggle = nil gGamepadRotateToggle = nil gGamepadCloneFlag = nil uiHover = nil modeList = nil modeIndex = nil modeChangeTimer = nil modeId = nil camera = nil cameraX = nil cameraY = nil cameraMotion = nil sphereSize = nil material = nil orgShape = nil axisAligned = nil axis = nil brush = nil brushShape = nil brushSize = nil brushType = nil undo = nil trigUndo = nil materialBrush = nil selectedShape = nil selectShapeTransform = nil selectPivot = nil selectTouching = nil grid = nil mousePointer = nil inputErase = nil registerShortCut = nil square = nil saveDialog = nil moveSpeed = nil pick = nil gToolSelectWidth = nil gToolSelectAlpha = 0 gToolGroupScrollPos = nil gToolGroupScrollSmooth = nil UP_KEY = nil DOWN_KEY = nil gSelectedTool = nil gSelectedToolFlash = 1 gSelectedToolGroup = nil toolSelect = nil hotKey = nil isGamepad = nil oldBodyPos = nil alignFlag = nil PaletteWindow = nil SaveDialogWindow = nil function init() active = false drawShape = 0 drawPoint = Vec(0,0,0) drawStartPos = Vec(0,0,0) drawNormal = Vec(0,0,0) canDraw = false pickShape = 0 gGamepadAlignToggle = false gGamepadRotateToggle = false gGamepadCloneFlag = false uiHover = false modeList = { {id="draw", name="loc@UI_TEXT_TOOL_DRAW", group=1}, {id="line", name="loc@UI_TEXT_TOOL_LINE", group=1}, {id="box", name="loc@UI_TEXT_TOOL_BOX", group=1}, {id="extrude", name="loc@UI_TEXT_TOOL_EXTRUDE", group=2}, {id="paint", name="loc@UI_TEXT_TOOL_PAINT", group=2}, {id="shape", name="loc@UI_TEXT_TOOL_SHAPE", group=3}, } modeIndex = 1 modeChangeTimer = 0 modeId = modeList[modeIndex].id camera = GetPlayerCameraTransform() cameraX = 0 cameraY = 0 cameraMotion = Vec(0,0,0) sphereSize = 5 material = 57 -- First wood material orgShape = 0 axisAligned = false axis = Vec(0, 1, 0) brush = 0 brushShape = 0 brushSize = 0 brushType = "sphere" undo = {} undo.available = false undo.newShape = 0 undo.oldShape = 0 undo.danglingShapes = {} trigUndo = false materialBrush = "" selectedShape = 0 selectTouching = nil selectShapeTransform = Transform() selectPivot = Transform() grid = false mousePointer = false inputErase = false registerShortCut = false square = LoadSprite("gfx/white.png") saveDialog = false moveSpeed = 0.002 pick = false gToolSelectWidth = 200 gToolSelectAlpha = 0 gToolGroupScrollPos = {1, 1, 1, 1, 1, 1} gToolGroupScrollSmooth = {1, 1, 1, 1, 1, 1} if LastInputDevice() == UI_DEVICE_GAMEPAD then --Quick automatic remapping for gamepad UP_KEY = "flashlight" DOWN_KEY = "jump" elseif string.upper(GetString("options.input.keymap.forward"))=="Z" and string.upper(GetString("options.input.keymap.left"))=="Q" then --Quick automatic remapping for AZERTY UP_KEY = "A" DOWN_KEY = "W" else UP_KEY = "Q" DOWN_KEY = "Z" end gSelectedTool = modeId gSelectedToolFlash = 1 gSelectedToolGroup = 0 toolSelect = false hotKey = "" isGamepad = LastInputDevice() == UI_DEVICE_GAMEPAD oldBodyPos = nil alignFlag = false end function handleCameraRot() if PaletteWindow:isVisible() or registerShortCut or saveDialog or gGamepadRotateToggle then return end local camx = InputValue("camerax")*180/math.pi local camy = InputValue("cameray")*180/math.pi cameraX = cameraX - camy cameraY = cameraY - camx if cameraX < -85 then cameraX = -85 end if cameraX > 85 then cameraX = 85 end if cameraY < -360 then cameraY = cameraY + 360 end if cameraY > 360 then cameraY = cameraY - 360 end camera.rot = QuatEuler(cameraX, cameraY, 0) end function handleCameraMotion() if PaletteWindow:isVisible() or registerShortCut or saveDialog then cameraMotion = Vec(0,0,0) return end if LastInputDevice() == UI_DEVICE_GAMEPAD then --Quick automatic remapping for gamepad UP_KEY = "flashlight" DOWN_KEY = "jump" elseif string.upper(GetString("options.input.keymap.forward"))=="Z" and string.upper(GetString("options.input.keymap.left"))=="Q" then --Quick automatic remapping for AZERTY UP_KEY = "A" DOWN_KEY = "W" else UP_KEY = "Q" DOWN_KEY = "Z" end if InputDown("up") or InputDown("down") or InputDown("left") or InputDown("right") or InputDown(UP_KEY) or InputDown(DOWN_KEY) then moveSpeed = math.min(moveSpeed + GetTimeStep()*0.008, 0.008) else moveSpeed = 0.002 end local d = moveSpeed local move = Vec(0,0,0) if InputDown("up") then move[3] = move[3] - d end if InputDown("down") then move[3] = move[3] + d end if InputDown("left") then move[1] = move[1] - d end if InputDown("right") then move[1] = move[1] + d end if InputDown(DOWN_KEY) and not InputDown("ctrl") then move[2] = move[2] - d end if InputDown(UP_KEY) then move[2] = move[2] + d end cameraMotion = VecScale(cameraMotion, 0.9) cameraMotion = VecAdd(cameraMotion, TransformToParentVec(camera, Vec(move[1], 0, move[3]))) cameraMotion[2] = cameraMotion[2] + move[2] camera.pos = VecAdd(camera.pos, cameraMotion) end function updateBrush(size) if brushShape ~= 0 then ClearShape(brushShape) TrimShape(brushShape) SetShapeLocalTransform(brushShape, Transform()) else brushShape = CreateShape(brush, Transform(), "creativemode.vox") SetShapeCollisionFilter(brushShape, 0, 0) end if size > 0 then local typ = brushType if materialBrush ~= "" then SetBrush(typ, size, "creativemode.vox", materialBrush) else SetBrush(typ, size, material) end local resized, offset = fitShape(brushShape, Vec(0,0,0), Vec(0,0,0), brushSize/2) if resized then DrawShapeLine(brushShape, offset[1], offset[2], offset[3], offset[1], offset[2], offset[3]) if brushType == "noise" then for i=1, brushSize do local x = math.random(-1, 1) local y = math.random(-1, 1) local z = math.random(-1, 1) DrawShapeLine(brushShape, offset[1]+x, offset[2]+y, offset[3]+z, offset[1]+x, offset[2]+y, offset[3]+z) end end end end end function update() if active then handleCameraMotion() end end function getShapeCoord(shape, worldPos) local lp = TransformToLocalPoint(GetShapeWorldTransform(shape), worldPos) lp[1] = math.floor(lp[1]/VOXEL_SIZE) lp[2] = math.floor(lp[2]/VOXEL_SIZE) lp[3] = math.floor(lp[3]/VOXEL_SIZE) return lp end function clearUndo() if undo.available then Delete(undo.oldShape) undo.oldShape = 0 TrimShape(undo.newShape) local x,y,z = GetShapeSize(undo.newShape) if x==0 and y==0 and z==0 then Delete(undo.newShape) end undo.newShape = 0 undo.available = false undo.danglingShapes = {} end end --Potentially resize shape to fit min/max coordinates with radius padding --Return coordinate offset function fitShape(shape, a, b, radius) local xs,ys,zs = GetShapeSize(shape) local xmi = math.min(math.min(a[1], b[1])-radius, 0) local ymi = math.min(math.min(a[2], b[2])-radius, 0) local zmi = math.min(math.min(a[3], b[3])-radius, 0) local xma = math.max(math.max(a[1], b[1])+radius, xs-1) local yma = math.max(math.max(a[2], b[2])+radius, ys-1) local zma = math.max(math.max(a[3], b[3])+radius, zs-1) local resized = true if xmi < 0 or ymi < 0 or zmi < 0 or xma > xs-1 or yma > ys-1 or zma > zs-1 then resized = ResizeShape(shape, xmi, ymi, zmi, xma, yma, zma) if not resized then SetString("hud.notification", GetTranslatedStringByKey("UI_HUD_NOTE_LOW_MEMORY")) end end return resized, Vec(-xmi, -ymi, -zmi) end function isCreativeModeEnabled() return GetBool("game.player.interactive") and (GetBool("level.spawn") or GetBool("options.game.spawn")) end function quit() gGamepadRotateToggle = false gGamepadAlignToggle = false gGamepadCloneFlag = false active = false selectMove = false camera.pos[2] = camera.pos[2] - 1.7 SetPlayerTransform(camera, true) Delete(brush) brush = 0 brushShape = 0 selectedShape = 0 clearUndo() InputResetOnTransition() SetBool("game.ui.hidemods", false) end function tick() -- handle the display of the palette window -- the window is already automatically closed by the menu_cancel or esc buttons isGamepad = LastInputDevice() == UI_DEVICE_GAMEPAD if SaveDialogWindow.isClosed and (not isGamepad and InputPressed("jump") or isGamepad and InputPressed("interact")) then HintsWindow:hide() PaletteWindow:Show(active) end HintsWindow:show(active and not pick and (not isGamepad or not PaletteWindow:isVisible())) local shortcut = GetString("savegame.creativemode.shortcut") if active and drawShape==0 and saveDialog==false then if InputPressed(shortcut) or InputPressed("pause") or PaletteWindow.isQuiting or (PaletteWindow:isVisible() and InputPressed("map")) then if not registerShortCut then if (InputPressed("pause") or (PaletteWindow:isVisible() and InputPressed("map"))) and pick then pick = false HintsWindow:show() elseif PaletteWindow.isQuiting or not PaletteWindow:isVisible() then quit() PaletteWindow:Hide() HintsWindow:hide() end end end else if isCreativeModeEnabled() and (InputPressed(shortcut) or PauseMenuButton("loc@UI_TEXT_CREATIVE_MODE")) then PaletteWindow:activate() HintsWindow:show(true) end end SetBool("game.creative.enabled", active) if active then handleCameraRot() SetPlayerTransform(Transform(VecAdd(camera.pos, Vec(0, -1.7, 0)), camera.rot), true) SetPlayerHealth(1.0) SetBool("game.gridshader", grid) SetBool("game.disablepause", true) SetBool("game.disablemap", true) SetBool("game.player.disableinput", true) SetCameraTransform(camera) SetCameraDof(10,0) local allowGrab = (modeId ~= "paint" and modeId ~= "shape") local inputFollow = InputDown("grab") or modeId =="paint" local inputPressed = InputPressed("usetool") or (allowGrab and InputPressed("grab")) local inputDown = InputDown("usetool") or (allowGrab and InputDown("grab")) if drawShape == 0 then local modifier = false if LastInputDevice() == UI_DEVICE_GAMEPAD then modifier = InputDown("crouch") else modifier = InputDown("shift") end inputErase = modifier and modeId ~= "paint" end if drawShape ~= 0 or InputDown("alt") or LastInputDevice() == UI_DEVICE_GAMEPAD then local mw = InputValue("mousewheel") if LastInputDevice() == UI_DEVICE_GAMEPAD then mw = (InputPressed("tool_group_next") and 1 or 0) - (InputPressed("tool_group_prev") and 1 or 0) end if mw ~= 0 then sphereSize = sphereSize + mw if sphereSize > 16 then sphereSize = 16 end if sphereSize < 1 then sphereSize = 1 end end end local desiredBrushSize = sphereSize if modeId == "box" then desiredBrushSize = 1 end if desiredBrushSize ~= brushSize or forceUpdateBrush then brushSize = desiredBrushSize forceUpdateBrush = false updateBrush(brushSize) end if pick then inputPressed = false inputDown = false if InputPressed("usetool") then local pos = GetCameraTransform().pos local mousePosX, mousePosY = UiGetMousePos() local dir = UiPixelToWorld(mousePosX, mousePosY) QueryRequire("visible") local hit, dist, n, shape = QueryRaycast(pos, dir, 1000) if hit then local p = VecAdd(VecAdd(pos, VecScale(dir, dist)), VecScale(n, -0.05)) local pickType, pickRed, pickGreen, pickBlue = GetShapeMaterialAtPosition(shape, p) local palette = GetShapePalette(brushShape) for i=1, #palette do local t,r,g,b = GetShapeMaterial(brushShape, palette[i]) if r == pickRed and g == pickGreen and b == pickBlue and t == pickType then pick = false material = palette[i] brushType = "sphere" forceUpdateBrush = true materialBrush = "" return end end end end end if PaletteWindow:isVisible() then inputPressed = false inputDown = false end if InputDown("ctrl") and InputPressed("z") and undo.available then trigUndo = true end local ct = GetCameraTransform() local dir if mousePointer then local mousePosX, mousePosY = UiGetMousePos() dir = UiPixelToWorld(mousePosX, mousePosY) else dir = TransformToParentVec(ct, Vec(0, 0, -1)) end QueryRejectBody(brush) if drawShape ~= 0 then QueryRejectShape(drawShape) else QueryRequire("physical visible") end local hit, dist, normal, shape = QueryRaycast(ct.pos, dir, MAX_DIST) pickShape = shape local hitPos = VecAdd(ct.pos, VecScale(dir, dist)) if hit and grid and modeId~="shape" then local shapeTransform = GetShapeWorldTransform(shape) local localPos = TransformToLocalPoint(shapeTransform, VecAdd(hitPos, VecScale(normal, -0.5*VOXEL_SIZE))) local localNormal = TransformToLocalVec(shapeTransform, normal) for i=1, 3 do localPos[i] = (math.floor(localPos[i]/VOXEL_SIZE)+0.5)*VOXEL_SIZE end localPos = VecAdd(localPos, VecScale(localNormal, VOXEL_SIZE*0.5)) local t = Transform(localPos) if math.abs(localNormal[1]) > 0.5 then t.rot = QuatEuler(0, 90, 0) elseif math.abs(localNormal[2]) > 0.5 then t.rot = QuatEuler(90, 0, 0) end t = TransformToParentTransform(shapeTransform, t) DrawSprite(square, t, 0.1, 0.1, 1, 0, 0, 0.25) end if InputDown("shift") or modeId == "extrude" or modeId == "paint" then hitPos = VecAdd(hitPos, VecScale(normal, -0.5*VOXEL_SIZE)) else hitPos = VecAdd(hitPos, VecScale(normal, 0.5*VOXEL_SIZE)) end local showBrush = (hit and (drawShape==0 or inputErase)) if modeId == "extrude" or modeId == "shape" or pick then showBrush = false end if (modeId == "box" or modeId == "line") and drawShape ~= 0 and inputErase then showBrush = false end if grid and (modeId == "box" or brushSize==1) then showBrush = false end if modeId == "paint" then showBrush = shape ~= 0 and HasTag(shape, "creative") end if InputDown("m") then showBrush = false end if showBrush then local shapeTransform = GetShapeWorldTransform(shape) local newPos = TransformToLocalPoint(shapeTransform, hitPos) newPos[1] = math.floor(newPos[1]/VOXEL_SIZE)*VOXEL_SIZE newPos[2] = math.floor(newPos[2]/VOXEL_SIZE)*VOXEL_SIZE newPos[3] = math.floor(newPos[3]/VOXEL_SIZE)*VOXEL_SIZE local bp = TransformToParentPoint(shapeTransform, newPos) local current = GetBodyTransform(brush) local t = Transform(bp, shapeTransform.rot) if current.pos[2] > -50 then t.pos = VecLerp(current.pos, t.pos, 0.5) end SetBodyTransform(brush, t) if inputErase then SetTag(brushShape, "invisible") DrawBodyOutline(brush,1,1,1,1) else RemoveTag(brushShape, "invisible") end else SetBodyTransform(brush, Transform(Vec(0, -100, 0))) end if trigUndo then if drawShape == 0 and undo.available then CopyShapeContent(undo.oldShape, undo.newShape) SetShapeLocalTransform(undo.newShape, GetShapeLocalTransform(undo.oldShape)) for i=1, #undo.danglingShapes do Delete(undo.danglingShapes[i]) end clearUndo() end trigUndo = false end if modeId == "shape" and selectedShape ~= 0 then if selectMove then if selectTouching ~= 0 then DrawShapeOutline(selectedShape, 0,0,1,1) end else DrawShapeOutline(selectedShape, 1,1,1,0.5) end end canDraw = false if shape ~= 0 then if modeId=="extrude" or modeId=="shape" or modeId=="paint" or pick then if shape~=0 and HasTag(shape, "creative") then canDraw = true end else canDraw = true end end if modeId == "shape" and not uiHover and not saveDialog then if selectedShape ~= 0 then -- delete action local deleteFlag = false if LastInputDevice() == UI_DEVICE_GAMEPAD then -- crouch is toggle, so we need to check both pressed and released deleteFlag = InputPressed("crouch") or InputReleased("crouch") else deleteFlag = InputPressed("delete") end -- save action local saveFlag = false if LastInputDevice() == UI_DEVICE_GAMEPAD then saveFlag = InputPressed("tool_group_next") else saveFlag = InputDown("ctrl") and InputPressed("S") end if saveFlag and shape == selectedShape then SaveDialogWindow:setText(GetTagValue(selectedShape, "creative")) SaveDialogWindow:Show(true) end if InputPressed("usetool") and shape == selectedShape then if InputDown("ctrl") then local t = GetShapeLocalTransform(selectedShape) local newShape = CreateShape(GetShapeBody(selectedShape), t, selectedShape) CopyShapeContent(selectedShape, newShape) SetTag(newShape, "creative") SetTag(newShape, "inherittags") selectedShape = newShape end local st = GetShapeWorldTransform(selectedShape) selectShapeTransform = TransformToLocalTransform(ct, st) selectPivot = TransformToLocalPoint(ct, hitPos) selectMove = true end -- clone with gamepad, but not holding "delete button" if LastInputDevice() == UI_DEVICE_GAMEPAD and InputPressed("grab") and shape == selectedShape and not deleteFlag then local t = GetShapeLocalTransform(selectedShape) local newShape = CreateShape(GetShapeBody(selectedShape), t, selectedShape) CopyShapeContent(selectedShape, newShape) SetTag(newShape, "creative") SetTag(newShape, "inherittags") selectedShape = newShape local st = GetShapeWorldTransform(selectedShape) selectShapeTransform = TransformToLocalTransform(ct, st) selectPivot = TransformToLocalPoint(ct, hitPos) gGamepadCloneFlag = true selectMove = true end if selectMove and (InputDown("usetool") or (InputDown("grab") and gGamepadCloneFlag))then if LastInputDevice() == UI_DEVICE_GAMEPAD then if InputPressed("zoom") then gGamepadRotateToggle = not gGamepadRotateToggle end if gGamepadRotateToggle then local vx = InputValue("camerax") local vy = InputValue("cameray") if math.abs(vx) + math.abs(vy) > 0 then local rot = Transform(selectPivot, Quat()) local tmp = TransformToLocalTransform(rot, selectShapeTransform) rot.rot = QuatEuler(vy*10, vx*10, 0) selectShapeTransform = TransformToParentTransform(rot, tmp) end end else gGamepadRotateToggle = false local v = InputValue("mousewheel") if math.abs(v) > 0 then local rot = Transform(selectPivot, Quat()) local tmp = TransformToLocalTransform(rot, selectShapeTransform) if InputDown("shift") then rot.rot = QuatEuler(0, 0, -v*10) else rot.rot = QuatEuler(-v*10, 0, 0) end selectShapeTransform = TransformToParentTransform(rot, tmp) end end local t = TransformToParentTransform(ct, selectShapeTransform) local b = GetShapeBody(selectedShape) local bt = GetBodyTransform(b) local st = TransformToLocalTransform(bt, t) SetShapeLocalTransform(selectedShape, st) SetBodyActive(b, false) local mi, ma = GetShapeBounds(selectedShape) QueryRejectShape(selectedShape) QueryRequire("visible") local s = QueryAabbShapes(mi, ma) selectTouching = 0 for i=1,#s do if IsShapeTouching(selectedShape, s[i]) then selectTouching = s[i] end end end -- we check deleteFlag to release grabbing if we are want to delete a shape if InputReleased("usetool") or (InputReleased("grab") and gGamepadCloneFlag) or deleteFlag then gGamepadRotateToggle = false if (InputReleased("grab") and gGamepadCloneFlag) then gGamepadCloneFlag = false end if selectMove then selectMove = false SetBodyActive(GetShapeBody(selectedShape), true) if selectTouching == 0 then --Create new dynamic body and move shape to it local body = GetShapeBody(selectedShape) if #GetBodyShapes(body)>1 then local entities = Spawn("", GetBodyTransform(GetShapeBody(selectedShape))) body = entities[1] SetShapeBody(selectedShape, body) end SetBodyDynamic(body, true) clearUndo() else --Move shape to the touching body local touchingBody = GetShapeBody(selectTouching) local st = GetShapeWorldTransform(selectedShape) st = TransformToLocalTransform(GetBodyTransform(touchingBody), st) SetShapeBody(selectedShape, touchingBody, st) SetBodyActive(touchingBody, true) clearUndo() end selectTouching = 0 end end if deleteFlag then Delete(selectedShape) selectedShape = 0 end end if InputPressed("usetool") then if shape~=0 and HasTag(shape, "creative") then if undo.available then local tmp = MergeShape(undo.newShape) if tmp ~= undo.newShape then if shape == undo.newShape then shape = tmp end undo.newShape = tmp end end clearUndo() selectedShape = shape else selectedShape = 0 end end return end --Initial press down if canDraw and inputPressed and not uiHover then if hit then if undo.available and HasTag(undo.newShape, "creative") then local tmp = MergeShape(undo.newShape) if tmp ~= undo.newShape then if shape == undo.newShape then shape = tmp end undo.newShape = tmp end end clearUndo() drawPoint = TransformToLocalPoint(ct, hitPos) drawStartPos = hitPos drawNormal = normal if HasTag(shape, "creative") or inputErase then drawShape = shape if not inputErase then --Make sure shape uses creative mode palette CopyShapePalette(brushShape, drawShape) end else local body = GetShapeBody(shape) local bodyTransform = GetBodyTransform(body) local shapeTransform = GetShapeLocalTransform(shape) local newPos = TransformToLocalPoint(bodyTransform, hitPos) newPos = TransformToLocalPoint(shapeTransform, newPos) newPos[1] = math.floor(newPos[1]/VOXEL_SIZE)*VOXEL_SIZE newPos[2] = math.floor(newPos[2]/VOXEL_SIZE)*VOXEL_SIZE newPos[3] = math.floor(newPos[3]/VOXEL_SIZE)*VOXEL_SIZE newPos = TransformToParentPoint(shapeTransform, newPos) drawShape = CreateShape(body, Transform(newPos, shapeTransform.rot), "creativemode.vox") SetTag(drawShape, "creative") SetTag(drawShape, "inherittags") end oldBodyPos = TransformToLocalPoint(GetBodyTransform(GetShapeBody(drawShape)), drawStartPos) orgShape = CreateShape(GetShapeBody(drawShape), GetShapeLocalTransform(drawShape), drawShape) CopyShapeContent(drawShape, orgShape) SetTag(orgShape, "invisible") undo.available = true undo.newShape = drawShape undo.oldShape = orgShape end end --While pressing button if drawShape ~= 0 then if modeId ~= "shape" and inputDown and GetBool("game.lowmemory") then SetString("hud.notification", GetTranslatedStringByKey("UI_HUD_NOTE_LOW_MEMORY")) elseif modeId ~= "shape" and inputDown then if modeId == "line" or modeId == "extrude" or modeId == "box" then if orgShape ~= 0 then CopyShapeContent(orgShape, drawShape) SetShapeLocalTransform(drawShape, GetShapeLocalTransform(orgShape)) end end local drawPos = TransformToParentPoint(ct, drawPoint) if mousePointer then drawPos = VecAdd(ct.pos, VecScale(dir, VecLength(drawPoint))) end if inputFollow and hit then drawPos = VecCopy(hitPos) end if modeId == "extrude" then drawPos = getClosestPointRayRay(drawStartPos, drawNormal, ct.pos, dir) end alignFlag = false if LastInputDevice() == UI_DEVICE_GAMEPAD then if InputPressed("zoom") then gGamepadAlignToggle = not gGamepadAlignToggle end alignFlag = gGamepadAlignToggle and modeId == "line" else alignFlag = InputDown("ctrl") and modeId == "line" end if alignFlag then local st = GetShapeWorldTransform(drawShape) axis = TransformToLocalVec(st, VecSub(drawPos, drawStartPos)) if math.abs(axis[1]) < math.abs(axis[2]) or math.abs(axis[1]) < math.abs(axis[3]) then axis[1] = 0 end if math.abs(axis[2]) < math.abs(axis[1]) or math.abs(axis[2]) < math.abs(axis[3]) then axis[2] = 0 end if math.abs(axis[3]) < math.abs(axis[1]) or math.abs(axis[3]) < math.abs(axis[2]) then axis[3] = 0 end axis = TransformToParentVec(st, VecNormalize(axis)); gGamepadAlignToggle = true axisAligned = true else gGamepadAlignToggle = false axisAligned = false end if axisAligned then drawPos = getClosestPointRayRay(drawStartPos, axis, ct.pos, dir) end local coord = getShapeCoord(drawShape, drawPos) local startCoord = getShapeCoord(drawShape, drawStartPos) if inputErase then SetBrush(brushType, brushSize, 0) else if materialBrush ~= "" then SetBrush(brushType, brushSize, "creativemode.vox", materialBrush) else SetBrush(brushType, brushSize, material) end end if modeId == "line" then local canDrawLine = true if not inputErase then local resized, offset = fitShape(drawShape, startCoord, coord, brushSize/2) canDrawLine = resized startCoord = VecAdd(startCoord, offset) coord = VecAdd(coord, offset) end if canDrawLine then DrawShapeLine(drawShape, startCoord[1], startCoord[2], startCoord[3], coord[1], coord[2], coord[3], false, true) end elseif modeId == "box" then local canDrawBox = true if not inputErase then local resized, offset = fitShape(drawShape, startCoord, coord, 0) canDrawBox = resized startCoord = VecAdd(startCoord, offset) coord = VecAdd(coord, offset) end if canDrawBox then DrawShapeBox(drawShape, startCoord[1], startCoord[2], startCoord[3], coord[1], coord[2], coord[3]) end elseif modeId == "extrude" then local st = GetShapeWorldTransform(drawShape) local n = TransformToLocalVec(st, drawNormal) n[1] = math.floor(n[1]+0.5) n[2] = math.floor(n[2]+0.5) n[3] = math.floor(n[3]+0.5) local steps = math.floor(VecDot(n, VecSub(coord, startCoord))) if inputErase then if steps > -1 then steps = -1 end else if steps < 1 then steps = 1 end end if steps > 0 or (inputErase and steps < 0) then local extrudeMode = "color" if inputFollow then extrudeMode = "geometry" end local canDrawExtrude = true if not inputErase then local resized, offset = fitShape(drawShape, startCoord, VecAdd(startCoord, VecScale(n, steps)), 0) canDrawExtrude = resized startCoord = VecAdd(startCoord, offset) end if canDrawExtrude then ExtrudeShape(drawShape, startCoord[1], startCoord[2], startCoord[3], n[1], n[2], n[3], steps, extrudeMode) end end else if inputFollow==false or hit then local oldCoord = getShapeCoord(drawShape, TransformToParentPoint(GetBodyTransform(GetShapeBody(drawShape)), oldBodyPos)) if inputFollow==false or VecLength(VecSub(oldCoord, coord)) < sphereSize+5 then local canDrawExtrude = true if inputErase==false and modeId ~= "paint" then local resized, offset = fitShape(drawShape, oldCoord, coord, brushSize/2) canDrawExtrude = resized oldCoord = VecAdd(oldCoord, offset) coord = VecAdd(coord, offset) end if canDrawExtrude then DrawShapeLine(drawShape, oldCoord[1], oldCoord[2], oldCoord[3], coord[1], coord[2], coord[3], modeId == "paint", true) oldBodyPos = TransformToLocalPoint(GetBodyTransform(GetShapeBody(drawShape)), drawPos) end end end end if inputErase and (modeId=="box" or modeId=="line") then local t = GetShapeWorldTransform(orgShape) t.pos = TransformToParentPoint(t, VecScale(startCoord, VOXEL_SIZE)) SetBodyTransform(brush, t) ClearShape(brushShape) TrimShape(brushShape) SetShapeLocalTransform(brushShape, Transform()) local typ = brushType SetBrush(typ, brushSize, material) local size = VecSub(coord, startCoord) if modeId=="box" then local resized, offset = fitShape(brushShape, Vec(0,0,0), Vec(size[1], size[2], size[3]), 0) if resized then DrawShapeBox(brushShape, offset[1], offset[2], offset[3], offset[1]+size[1], offset[2]+size[2], offset[3]+size[3]) end else local resized, offset = fitShape(brushShape, Vec(0,0,0), Vec(size[1], size[2], size[3]), brushSize/2) if resized then DrawShapeLine(brushShape, offset[1], offset[2], offset[3], offset[1]+size[1], offset[2]+size[2], offset[3]+size[3], false, true) end end SetTag(brushShape, "invisible") DrawBodyOutline(brush,1,1,1,1) end local b = GetShapeBody(drawShape) if IsBodyDynamic(b) then SetBodyActive(b, true) end else orgShape = 0 undo.danglingShapes = SplitShape(drawShape, (modeId == "draw" and materialBrush == "foliage" and brushSize > 1)) TrimShape(drawShape) drawShape = 0 if inputErase and (modeId == "box" or modeId == "line") then forceUpdateBrush = true end end end end end gToolSelectAlpha = 0 function drawTool() UiPush() UiTranslate(0, UiHeight()) UiAlign("top left") local currentTool = modeId if not gSelectedTool then gSelectedTool = currentTool gSelectedToolFlash = 1 gSelectedToolGroup = 0 else if gSelectedTool ~= currentTool then gSelectedTool = currentTool gSelectedToolFlash = 1 SetValue("gSelectedToolFlash", 0, "easeout", 0.3) end end if InputValue("mousewheel") ~= 0 then gSelectedToolGroup = 0 end for i=1,6 do if InputDown(""..i) then gSelectedToolGroup = i end end local groupName = {"Basic", "Modify", "Misc"} if gToolSelectAlpha == 0 and toolSelect then SetValue("gToolSelectAlpha", 1, "easeout", 0.25) end if gToolSelectAlpha == 1 and not toolSelect then SetValue("gToolSelectAlpha", 0, "easein", 0.25) end if gToolSelectAlpha > 0 then UiPush() UiColorFilter(1,1,1, gToolSelectAlpha) UiScale(1, gToolSelectAlpha) local w = 200 local windowToolWidth = 238 local toolSelectWidth = gToolSelectWidth local groupIdFrameWidth = 32 gToolSelectWidth = 0 UiTranslate(16,0) for g=1, #groupName do local empty = true UiPush() local offset = not IsRunningOnPC() and not IsRunningOnMac() and groupIdFrameWidth + 15 or 0 UiTranslate(0, -106 - 15 + offset) UiTranslate(UiCenter() - toolSelectWidth / 2 + windowToolWidth + 20, 0) local toolList = {} for i=1, #modeList do if modeList[i].group==g then toolList[#toolList+1] = modeList[i] end end --table.sort(toolList, function(a, b) return a.index < b.index end) if #toolList > 5 then local current = 1 for i=1, #toolList do if currentTool == toolList[i].id then current = i end end if current < gToolGroupScrollPos[g]+2 then gToolGroupScrollPos[g] = math.max(1, gToolGroupScrollPos[g] - 1) end if current > gToolGroupScrollPos[g]+2 then gToolGroupScrollPos[g] = math.min(#toolList-4, gToolGroupScrollPos[g] + 1) end else gToolGroupScrollPos[g] = 1 end Ui:BoldFont(22) UiAlign("center") UiTextAlignment("center") UiWordWrap(windowToolWidth) local toolItemWidth = 270 local toolItemHeight = 38 UiPush() local groupNumberPos = -106 - groupIdFrameWidth UiTranslate(-0.5 * toolItemWidth, -6.5 * toolItemHeight + (#toolList > 5 and 10 or 0)) UiWindow(toolItemWidth, 5.5 * toolItemHeight, #toolList > 5) UiTranslateToScreenBottomEdge(0, groupNumberPos + 20) if #toolList > 5 then UiTranslate(0, (#toolList - 5)*toolItemHeight + 4) end local diff = gToolGroupScrollPos[g] - gToolGroupScrollSmooth[g] gToolGroupScrollSmooth[g] = gToolGroupScrollSmooth[g] + diff * 0.2 UiTranslate(0.5 * toolItemWidth, -toolItemHeight*(gToolGroupScrollSmooth[g]-1)) for i=#toolList, 1,-1 do local id = toolList[i].id local key = toolList[i].name local name = key if HasTranslationByKey(key) then name = GetTranslatedStringByKey(key) end local nameWidth, nameHeight = UiGetTextSize(name) toolItemHeight = nameHeight + 15 UiTranslate(0, -toolItemHeight) if nameWidth > windowToolWidth then windowToolWidth = nameWidth end UiPush() if id == currentTool then UiTranslate(-0.75 * windowToolWidth, -0.75 * toolItemHeight) UiClipRect(1.5 * windowToolWidth, 1.5 * toolItemHeight) UiTranslate(0.75 * windowToolWidth, 0.75 * toolItemHeight) UiTranslate(0, -5) UiScale(1+gSelectedToolFlash*0.4) UiTranslate(0, 5) UiPush() UiTranslate(0, -UiFontHeight()) UiColor(0,0,0,0.4) UiImageBox("ui/common/box-solid-10.png", windowToolWidth, toolItemHeight, 10, 10) UiPop() UiColor(1,1,0.5,1) else if g ~= gSelectedToolGroup then UiColorFilter(1, 1, 1, 0.6) end UiColor(1,1,1) end UiAlign("center bottom") local linesCount = math.floor(nameHeight / UiFontHeight()) linesCount = linesCount < 1 and 1 or linesCount UiTranslate(0, 2 * (nameHeight - UiFontHeight()) + 0.3 * UiFontHeight() - (linesCount - 1) * UiFontHeight()) UiText(name) UiPop() end w = windowToolWidth + 20 UiPop() UiPop() if #toolList > 0 then empty = false UiPush() if IsRunningOnPC() or IsRunningOnMac() then if g ~= gSelectedToolGroup then UiColorFilter(1, 1, 1, 0.6) end UiTranslate(0, groupNumberPos) UiTranslate(UiCenter()-toolSelectWidth/2 + windowToolWidth + 20, 0) UiAlign("center") UiTextAlignment("center") UiTranslate(-toolItemWidth/2, 0) UiImageBox("ui/common/box-solid-6.png", groupIdFrameWidth, groupIdFrameWidth, 6, 6) UiColorFilter(1,1,1,1) UiColor(0,0,0) UiTranslate(0, 24) UiWordWrap(groupIdFrameWidth) Ui:BoldFont(22) UiText(tostring(g)) end UiPop() end if not empty then UiTranslate(w + 20, 0) gToolSelectWidth = gToolSelectWidth + w + 20 end end UiPop() end UiTranslate(UiCenter(), -20) UiFont("bold.ttf", 26) UiAlign("center") local modeLocName = modeList[modeIndex].name if pick then UiText("loc@UI_CREATIVE_PICK_MATERIAL_UPPERCASE") else if modeLocName then UiText(GetTranslatedStringByKey(modeLocName.."_UPPERCASE")) end if modeId ~= "shape" then local _,r,g,b = GetShapeMaterial(brushShape, material) if material == 255 then if materialBrush == "foliage" then r,g,b = 0.3,0.6,0.3 end if materialBrush == "brick" then r,g,b = 0.6,0.3,0.2 end end UiTranslate(0, -42) UiAlign("center middle") UiColor(1,1,1) UiImageBox("ui/common/box-solid-6.png", 64, 26, 6, 6) UiColor(r,g,b) UiImageBox("ui/common/box-solid-6.png", 60, 22, 6, 6) end end UiPop() end EventMonitor = Ui.Window { id = "eventMonitor", x = 0, y = 0, w = 1920, h = 1080, animator = {playTime = 0}, interactable = false, modal = false, blur = false, ------------------------------------- signal = { onLogicUpdate = 0, onDeviceChanged = 1, onKeyPressed = 2, onButtonPressed = 3 }, ------------------------------------- onCreate = function(self) self:show(true) end, onDraw = function(self) end, onLogicUpdate = function(self) self:emit(self.signal.onLogicUpdate) local currentDevice = LastInputDevice() if currentDevice ~= self.lastDevice then self:emit(self.signal.onDeviceChanged, currentDevice) self.lastDevice = currentDevice if currentDevice == UI_DEVICE_GAMEPAD then self.isGamepad = true elseif currentDevice == UI_DEVICE_MOUSE then self.isGamepad = false end end -- only if creativemode is active if active then if self.lastDevice == UI_DEVICE_GAMEPAD then self.isGamepad = true UiSetCursorState(UI_CURSOR_HIDE_AND_LOCK) elseif self.lastDevice == UI_DEVICE_MOUSE then self.isGamepad = false end end end, Ui.Frame{Ui.Text{id = "fakeobject",visible = false}} } local ico = {} ico.__index = function(_, key) local ico = Ui.ActionIcons[key] ico.style = "white" return ico end setmetatable(ico, ico) local iconsData = { pc = { wheel = ico.wheel, alt = ico.alt, space = ico.space, lmb = ico.lmb, rmb = ico.rmb, ctrl = ico.ctrl, shift = ico.shift, del = ico.del, }, gamepad = { player = {} }, } setmetatable(iconsData.pc, { __index = function(_,key) return ico["key:"..key] end }) setmetatable(iconsData.gamepad, { __index = function(_, key) return ico["player"..":"..key] end }) local function getIcon(device, key) if iconsData[device] then return iconsData[device][key] end return nil end local deviceIndex = { __index = function(table, key) return getIcon(table.device, key) end } local icons = { __index = function(_, device) if device == 'pc' or device == 'gamepad' then return setmetatable({device = device}, deviceIndex) end return nil end } setmetatable(icons, icons) function binding(t) local ico = {} setmetatable(ico, { __index = function(_, key) return LastInputDevice() == UI_DEVICE_GAMEPAD and t.gamepad[key] or t.pc[key] end } ) ico.id = "ico" local icons = Ui.Frame { id = "icons", ico, onCreate = function(self) self.getSize = function(self) return ico:getSize() end end, } local layout = Ui.VerticalLayout { onCreate = function(self) self.icons = self:findById("icons") self.spacer = self:findById("spacer") local originalGetSize = self.getSize self.getSize = function(self) originalGetSize(self) local child = self.children[1] return child.w, child.h end end, Ui.HorizontalLayout { alignment = Ui.ContentLayout.Vertical.Middle, padding = 10, Ui.Frame { w = 0, h = 0, id = "spacer", }, icons, Ui.Text { colorFilter = {1,1,1,0.5}, text = t.text, } } } t.draw = function(self) layout:draw() end t.getSize = function(self) layout.icons.ico = LastInputDevice() == UI_DEVICE_GAMEPAD and t.gamepad or t.pc return layout:getSize() end t.setIconsWidth = function(self, w) layout.spacer.w = w - icons.getSize() end t.getIconsWidth = function(self) return icons:getSize() end return t end Ui.Object:new{ onCreate = function(self) actionMap = { up_down = binding{ pc = icons.pc.Q / icons.pc.Z, gamepad = icons.gamepad.flashlight / icons.gamepad.jump, text = "loc@UI_CREATIVE_UP_DOWN" }, brush_size_1 = binding{ pc = icons.pc.wheel, gamepad = icons.gamepad.tool_group_prev / icons.gamepad.tool_group_next, text = "loc@UI_CREATIVE_BRUSH_SIZE" }, brush_size_2 = binding{ pc = icons.pc.alt + icons.pc.wheel, gamepad = icons.gamepad.tool_group_prev / icons.gamepad.tool_group_next, text = "loc@UI_CREATIVE_BRUSH_SIZE" }, palette = binding{ pc = icons.pc.space, gamepad = icons.gamepad.interact, text = "loc@UI_CREATIVE_PALETTE" }, paint_tool = binding{ pc = icons.pc.lmb, gamepad = icons.gamepad.usetool, text = "loc@UI_TEXT_TOOL_PAINT" }, creative_free = binding{ pc = icons.pc.lmb, gamepad = icons.gamepad.usetool, text = "loc@UI_CREATIVE_FREE" }, creative_projected = binding{ pc = icons.pc.rmb, gamepad = icons.gamepad.grab, text = "loc@UI_CREATIVE_PROJECTED" }, creative_align = binding{ pc = icons.pc.ctrl, gamepad = icons.gamepad.zoom, text = "loc@UI_CREATIVE_ALIGN" }, creative_by_color = binding{ pc = icons.pc.lmb, gamepad = icons.gamepad.usetool, text = "loc@UI_CREATIVE_BY_COLOR" }, creative_by_geometry = binding{ pc = icons.pc.rmb, gamepad = icons.gamepad.grab, text = "loc@UI_CREATIVE_BY_GEOMETRY" }, creative_select = binding{ pc = icons.pc.lmb, gamepad = icons.gamepad.usetool, text = "loc@UI_CREATIVE_SELECT" }, creative_pitch = binding { pc = icons.pc.wheel, gamepad = icons.gamepad.cameray, text = "loc@UI_CREATIVE_PITCH" }, creative_roll = binding{ pc = icons.pc.shift + icons.pc.wheel, gamepad = Ui.Object:new{}, text = "loc@UI_CREATIVE_ROLL" }, creative_yaw = binding{ pc = Ui.Object:new{}, gamepad = icons.gamepad.camerax, text = "loc@UI_CREATIVE_YAW" }, creative_move = binding{ pc = icons.pc.lmb, gamepad = icons.gamepad.usetool, text = "loc@UI_CREATIVE_MOVE" }, creative_clone = binding{ pc = icons.pc.ctrl, gamepad = icons.gamepad.grab, text = "loc@UI_CREATIVE_CLONE" }, creative_save = binding{ pc = icons.pc.ctrl + icons.pc.S, gamepad = icons.gamepad.tool_group_next, text = "loc@UI_CREATIVE_SAVE" }, creative_delete = binding{ pc = icons.pc.del, gamepad = icons.gamepad.crouch, text = "loc@UI_CREATIVE_DELETE" }, creative_erase = binding{ pc = icons.pc.shift, gamepad = icons.gamepad.crouch, text = "loc@UI_CREATIVE_ERASE", }, creative_draw = binding{ pc = icons.pc.shift, gamepad = icons.gamepad.crouch, text = "loc@UI_TEXT_TOOL_DRAW", }, creative_rotate_shape_toggle = binding{ pc = icons.pc.shift, gamepad = icons.gamepad.zoom, text = "loc@UI_CREATIVE_ROTATE_SHAPE" }, creative_rotate_camera_toggle = binding{ pc = icons.pc.shift, gamepad = icons.gamepad.zoom, text = "loc@UI_CREATIVE_CAMERA_TOGGLE" }, back = binding{ pc = icons.pc.Esc, gamepad = icons.gamepad.crouch, text = "loc@UI_BACK" }} end, } Ui.Object.check = true HintsWindow = Ui.Window { id = "hintsWindow", w = 1920, h = 1080, interactable = false, animator = {playTime = 0.0}, blur = false, onCreate = function(self) self:inject{ "mainFrame", } end, onDraw = function(self) self.mainFrame:prepare() end, Ui.Frame { w = 1920, h = 1080, id = "mainFrame", onCreate = function(self) self:inject{ "drawer", "background", } end, onPreDraw = function(self) self.isGamepad = LastInputDevice() == UI_DEVICE_GAMEPAD end, prepare = function(self) self.drawer:buildList() self.drawer.dryrun = true self.drawer:draw() self.drawer.dryrun = false local w = self.drawer.w + 10 local h = self.drawer.h self.background.w = w self.background.h = h self.x = UiCanvasSize().w - w - 30 self.y = UiHeight() - h - 30 end, Ui.ImageBox { id = "background", path = "ui/common/box-solid-6.png", w = 40, h = 40, border = {width = 6, height = 6}, color = {0,0,0,0.5}, }, Ui.Object:new { id = "drawer", hints = {}, dryrun = false, buildList = function(self) local hints = {} local am = actionMap if PaletteWindow:isVisible() then hints[#hints+1] = am.back else if hotKey and hotKey ~= "" then --hints[#hints+1] = keybind("loc@UI_CREATIVE_SHORTCUT") else hints[#hints+1] = am.up_down hints[#hints+1] = am.palette if modeId == "draw" or modeId == "line" or modeId == "paint" then if drawShape ~= 0 then hints[#hints+1] = am.brush_size_1 else hints[#hints+1] = am.brush_size_2 end end if modeId == "paint" then hints[#hints+1] = am.paint_tool end if modeId == "draw" or modeId == "line" or modeId == "box" then hints[#hints+1] = am.creative_free hints[#hints+1] = am.creative_projected end if modeId == "line" then hints[#hints+1] = am.creative_align end if modeId == "extrude" then hints[#hints+1] = am.creative_by_color hints[#hints+1] = am.creative_by_geometry end if modeId == "shape" then if selectedShape == 0 then hints[#hints+1] = am.creative_select else if selectMove then if self.parent.isGamepad then if gGamepadRotateToggle then hints[#hints+1] = am.creative_rotate_camera_toggle hints[#hints+1] = am.creative_pitch hints[#hints+1] = am.creative_yaw else hints[#hints+1] = am.creative_rotate_shape_toggle end else hints[#hints+1] = am.creative_pitch hints[#hints+1] = am.creative_roll end end hints[#hints+1] = am.creative_move hints[#hints+1] = am.creative_clone hints[#hints+1] = am.creative_save hints[#hints+1] = am.creative_delete end end if modeId ~= "paint" and modeId ~= "shape" then hints[#hints+1] = ((not isGamepad) or (not inputErase)) and am.creative_erase or am.creative_draw end end end self.hints = hints end, draw = function(self) local hints = self.hints UiPush() local icoSize = 42; local iconsWidth = 0 local paddingY = 10 local nY = 10 local function drawFrame() UiBeginFrame() UiTranslate(0, paddingY) local rowWidth = 0 for i = 1, 2 do UiPush() UiColorFilter(1,1,1, i == 2 and not self.dryrun and 1 or 0) for i=1, #hints do local hint = hints[i] rowWidth = math.max(rowWidth, hint:getSize()) local iconWidth = hint:getIconsWidth() iconsWidth = math.max(iconsWidth, iconWidth) hint:draw() UiTranslate(0, icoSize + nY) end for i=1, #hints do hints[i]:setIconsWidth(iconsWidth) end UiPop() end UiEndFrame() return rowWidth, #hints*(icoSize + nY) + 2 * paddingY - nY end UiPush() UiColorFilter(0,0,0,0) self.w, self.h = drawFrame() UiPop() if not self.dryrun then drawFrame() end UiPop() end }, }, } Ui.Screen = Ui.Object:new { onCreate = function(self) local canvas = UiSafeCanvasSize() self.w, self.h = canvas.w, canvas.h self.center = self.w / 2 self.middle = self.h / 2 end, } local CMButton = Teardown.GenericButton local CMColumn = Ui.VerticalLayout local CMRow = Ui.HorizontalLayout local saveDialogButton = function(obj) return CMButton{ w = 180, h = 42, _update = function(self) self.textOnly = isGamepad self.ignoreNavigation = isGamepad end, }:new(obj) end SaveDialogWindow = Ui.Window { id = "saveDialog", x = 0, y = 0, w = 580, h = 266, interactable = true, modal = true, animator = {playTime = 0.25}, blur = true, allowHide = true, clip = false, background = Ui.ImageBox { path = "ui/common/box-solid-10.png", border = {width = 10, height = 10}, color = {0,0,0,0.9} }, Show = function(self, flag) self.tasks:push(function() self:show(flag) end) end, Hide = function(self, flag) self.tasks:push(function() self:hide() end) end, onClose = function(self) saveDialog = false end, onShow = function(self) saveDialog = true end, onDraw = function(self) self.x = Ui.Screen.center - self.w / 2 self.y = Ui.Screen.middle - self.h / 2 isGamepad = LastInputDevice() == UI_DEVICE_GAMEPAD self.writeBtn.visible = isGamepad self.saveBtn.visible = not isGamepad or not self.textInput:empty() self.cancelBtn:update() self.writeBtn:update() self.saveBtn:update() end, onCreate = function(self) self:inject{ "cancelBtn", "saveBtn", "writeBtn", "textInput" } self.saveBtn.textInput = self.textInput self.writeBtn.textInput = self.textInput end, setText = function(self, text) self.textInput:setText(text) end, getText = function(self) return self.textInput:getText() end, Ui.Frame { id = "mainFrame", onCreate = function(self) self.w = SaveDialogWindow.w self.h = SaveDialogWindow.h end, padding = {top = 30, bottom = 30}, Ui.VerticalLayout { id = "verticalLayout", hAlign = Ui.ContentLayout.Horizontal.Center, alignment = Ui.ContentLayout.Horizontal.Center, padding = 30, Ui.Text { font = {"bold.ttf", 30}, text = "loc@UI_CREATIVE_SAVE_SHAPE_DIALOG_CAPTION" }, Ui.TextInput { id = "textInput", w = 430, h = 42, placeholder = {text = "loc@UI_TEXT_NAME"}, focus = true, input = {maxLength = 20}, margin = {bottom = 30}, }, CMRow { id = "buttons", hAlign = Ui.ContentLayout.Horizontal.Center, alignment = Ui.ContentLayout.Horizontal.Center, padding = 20, saveDialogButton { id = "cancelBtn", loc = Ui.loc"UI_BUTTON_CANCEL", ico = "[[menu:menu_cancel;iconsize=42,42]]", colorFilter = {1,1,1,0.8}, update = function(self) self:_update() self.text = self.loc:translate() if isGamepad then self.text = self.ico..self.text end end, onPressed = function(self) SaveDialogWindow:Hide() end }, saveDialogButton { id = "writeBtn", text = "Write name", colorFilter = {1,1,1,0.8}, button = "menu_accept", ico = "[[mmb;iconsize=42,42]]", loc = nil, onCreate = function(self) self.change_name = Ui.loc"UI_CREATIVE_SAVE_SHAPE_DIALOG_CHANGE_NAME" self.write_name = Ui.loc"UI_CREATIVE_SAVE_SHAPE_DIALOG_WRITE_NAME" end, update = function(self) self:_update() if isGamepad then local text = self.write_name if UiGetSymbolsCount(self.textInput:getText()) ~= 0 then text = self.change_name end self.text = self.ico..text:translate() end end, onPressed = function(self) self.textInput:onFocused() end }, saveDialogButton { id = "saveBtn", text = "loc@UI_CREATIVE_SAVE", loc = Ui.loc"UI_CREATIVE_SAVE", ico = "[[menu:interact;iconsize=42,42]]", colorFilter = {1,1,1,0.8}, button = "interact", update = function(self) self:_update() self.text = self.loc:translate() if isGamepad then self.text = self.ico..self.text self.button = "interact" else self.button = "" end self.disabled = UiGetSymbolsCount(self.textInput:getText()) == 0 end, onPressed = function(self) local saveName = self.textInput:getText() SetTag(selectedShape, "creative", saveName) local success, err = SaveShape(selectedShape, saveName) if not success then if err == "no_space" then SetString("hud.notification", GetTranslatedStringByKey("TEXT_NOT_ENOUGH_MEMORY")) elseif err == "invalid_name" then SetString("hud.notification", GetTranslatedStringByKey("TEXT_INVALID_NAME")) end end SaveDialogWindow:Hide() end }, }, } } } function draw(dt) PaletteWindow.tasks:execute() SaveDialogWindow.tasks:execute() if not SaveDialogWindow.isClosed then return end uiHover = false if active then if drawShape == 0 and not InputDown("alt") and not selectMove then local v = InputValue("mousewheel") if math.abs(v) > 0 then modeIndex = clamp(modeIndex - v, 1, #modeList) modeId = modeList[modeIndex].id selectedShape = 0 modeChangeTimer = 1 end end for i=1,3 do if InputPressed(tostring(i)) then local m = {} local c = 0 for j=1,#modeList do if modeList[j].group == i then m[#m+1] = modeList[j] if modeList[j].id == modeId and modeChangeTimer > 0 then c = #m end end end local n = 1 if c > 0 then n = (c % #m) + 1 end modeId = m[n].id modeChangeTimer = 1 end end drawTool() --Crosshair if not mousePointer then UiPush() UiTranslate(UiCenter(), UiMiddle()) UiAlign("center middle") UiImage("ui/hud/crosshair-dot.png") if canDraw and (modeId=="extrude" or modeId=="shape" or pick) then UiImage("ui/hud/crosshair-ring.png") if modeId=="shape" and InputDown("ctrl") and selectMove==false and selectedShape ~= 0 and InputDown("usetool")==false and pickShape == selectedShape then UiFont("regular.ttf", 26) UiTranslate(16, 16) UiText("+") end end UiPop() end end modeChangeTimer = math.max(modeChangeTimer - dt, 0) toolSelect = modeChangeTimer > 0 end local buttonFontSize = 22 local paletteButton = function(obj) local btn = CMButton { id = "button", w = 300, h = 38, fontSize = buttonFontSize * 1.23 } return btn:new(obj) end local selectableButton = function(obj) local btn = CMButton { id = "selectableButton", w = 300, h = 38, fontSize = buttonFontSize * 1.23, selected = false, on = obj.on == nil and "UI_TEXT_ON" or obj.on, switchable = true, onCreate = function(self,text) self.original = text end, onPressed = function(self) self.original = self.original or self.text local textOn = self.original if self.on and self.on ~= false then textOn = self.original.."_ON,".."loc@UI_TEXT_ON" end local textOff = self.original self.selected = not self.selected or self.switchable if self.selected then --self:setText(textOn) self.text = textOn else self.text = (textOff) end self:Selected(self.selected) self:setHovered(self.selected) end } return btn:new(obj) end function Palette(obj) local selectedMatColor = nil; local selectedText = nil; local locMaterial = { none="loc@UI_CREATIVE_NONE", glass="loc@UI_CREATIVE_GLASS", wood="loc@UI_CREATIVE_WOOD", masonry="loc@UI_CREATIVE_MASONRY", plaster="loc@UI_CREATIVE_PLASTER", metal="loc@UI_CREATIVE_METAL", heavymetal="loc@UI_CREATIVE_HEAVYMETAL", rock="loc@UI_CREATIVE_ROCK", dirt="loc@UI_CREATIVE_DIRT", foliage="loc@UI_CREATIVE_FOLIAGE", plastic="loc@UI_CREATIVE_PLASTIC", hardmetal="loc@UI_CREATIVE_HARDMETAL", hardmasonry="loc@UI_CREATIVE_HARDMASONRY", ice="loc@UI_CREATIVE_ICE", unphysical="loc@UI_CREATIVE_UNPHYSICAL" } local function PaletteRow(m,parent) local paletteRow = Ui.Frame { id = "paletteRow", w = 440, h = 42, onPressed = function(self) end, onMouseEnter = function(self) self.layout:onFrameEnter() end, onMouseLeave = function(self) self.layout:onFrameLeave() end, onHovered = function(self) end, layout = Ui.HorizontalLayout { id = "paletteHL", padding = 10, w = 400, h = 40, focused = false, frame = Ui.Frame { id = "rename me", hAlign = Ui.ContentLayout.Horizontal.Left, vAlign = Ui.ContentLayout.Vertical.Top, w = 200, h = 40, color = Ui.Colorf(1,1,1,0.2), counter = 0, hoverBox = Ui.ImageBox { path = "ui/common/box-solid-10.png", w = 200, h = 40, border = {width = 10, height = 10}, color = {hexrgb(0xD9D9D91E)}, visible = false }, materialName = Ui.Text{ id = "materialName", hAlign = Ui.ContentLayout.Horizontal.Center, vAlign = Ui.ContentLayout.Vertical.Middle, font = {"medium.ttf", 22}, text = m.name, regularColor = 0xFFFFFF, selectedColor = 0xFFFF7F, ignoreNavigation = true, onCreate = function(self) end, onEnter = function(self) if self ~= selectedText then if self == nil then return end self.prevColor = self.color self.color = Ui.Style.General.Color.Yellow end end, onLeave = function(self, force) if self ~= selectedText or force then self.color = self.prevColor end end, Selected = function(self) if selectedText and selectedText ~= self then selectedText.color = selectedText.regularColor end self.color = self.selectedColor selectedText = self end }, Hovered = function(self) self.counter = self.counter + 1 self.hoverBox.visible = true end, Unhovered = function(self) self.counter = self.counter - 1 self.hoverBox.visible = self.counter > 0 end }, onCreate = function(self) self:inject{"materialName"} self:connect(self.onFrameEnter, self.materialName, self.materialName.onEnter) self:connect(self.onFrameLeave, self.materialName, self.materialName.onLeave) self:connect(self.onFrameEnter, self.frame, self.frame.Hovered) self:connect(self.onFrameLeave, self.frame, self.frame.Unhovered) end, onColorFocused = function(self, matColor) matColor.hoveredBox:onMouseEnter() end, onColorUnfocused = function(self, matColor) matColor.hoveredBox:onMouseLeave() end, onColorPressed = function(self, matColor) matColor.hoveredBox:onPressed() self.frame.materialName:Selected() PaletteWindow:emit(PaletteWindow.signal.onPaletteButtonPressed) end, onFrameEnter = function(self) self:emit(self.onFrameEnter) end, onFrameLeave = function(self) self:emit(self.onFrameLeave) end, onGroupPressed = function(self) end, onPreDraw = function(self) if InputPressed("menu_left") then if self.firstChild == Ui.activeElement then UiNavSkipUpdate() end end end, }, } local layout = paletteRow:findById("paletteHL") for j=1, #m.entries do local id = m.entries[j].id local col = m.entries[j].color layout:append(Ui.NavComponent{Ui.Frame { w = 42, h = 42, Ui.ImageBox { hAlign = Ui.ContentLayout.Horizontal.Center, vAlign = Ui.ContentLayout.Vertical.Middle, id = "matColor", path = "ui/common/box-solid-4.png", w = 42, h = 42, border = {width = 4, height = 4}, color = col, }, Ui.ImageBox { hAlign = Ui.ContentLayout.Horizontal.Center, vAlign = Ui.ContentLayout.Vertical.Middle, id = "hoveredBox", path = "ui/common/box-outline-6.png", w = 42, h = 42, border = {width = 6, height = 6}, color = {0,0,0,0}, regularColor = {0,0,0,0}, hoveredColor = {hexrgb(0xFFFF7F)}, selectedColor = {1, 1, 1, 1}, visible = false, onPressed = function(self) if selectedMatColor and selectedMatColor ~= self then selectedMatColor.color = selectedMatColor.regularColor end selectedMatColor = self self.color = self.selectedColor end, onMouseEnter = function(self) self.visible = true if self ~= selectedMatColor then self.color = self.hoveredColor end end, onMouseLeave = function(self) self.visible = self == selectedMatColor if self ~= selectedMatColor and not self.visible then self.color = self.regularColor end end, }, color = col, isColorFrame = "colorFrame", id = tostring(id), onCreate = function(self) self:connect( "onEnter", parent, parent.onRowChanged ) self:inject{ "hoveredBox", "matColor", } end, onMouseEnter = function(self) self:dbgmsg("Enter: "..tostring(self)) layout:onColorFocused(self) if isGamepad then self:emit("onEnter", layout) end end, onMouseLeave = function(self) self:dbgmsg("Leave: "..tostring(self)) layout:onColorUnfocused(self) end, onPressed = function(self) self:dbgmsg("Pressed") pick = false material = id brushType = "sphere" forceUpdateBrush = true materialBrush = "" layout:onColorPressed(self) end }}) end layout.firstChild = layout:findById(tostring(m.entries[1].id)) return paletteRow:new(m) end local vl = Ui.VerticalLayout{ hAlign = Ui.ContentLayout.Horizontal.Center, padding = 18, selectedRow = nil, onCreate = function(self) PaletteWindow:connect( PaletteWindow.signal.onLogicUpdate, self, function(self) if Ui.activeElement == nil then self:clearSelection() end end ) end, clearMaterial = function(self) if selectedMatColor ~= nil then selectedMatColor.color = selectedMatColor.regularColor selectedMatColor = nil end end, clearText = function(self) if selectedText ~= nil then selectedText:onLeave(true) selectedText = nil end end, clearSelection = function(self) if self.selectedRow ~= nil then self.selectedRow:onFrameLeave() self.selectedRow = nil end end, onRowChanged = function(self, row) if self.selectedRow ~= nil and self.selectedRow ~= row then self.selectedRow:onFrameLeave() end if self.selectedRow ~= row and row ~= nil then row:onFrameEnter() end self.selectedRow = row end, } local first = true vl.onShapeChanged = function(self) if not first then do return end end first = false local mat = {} local p = GetShapePalette(brushShape) for i=1, #p do local t,r,g,b = GetShapeMaterial(brushShape, p[i]) if t ~= "none" then if not mat[t] then mat[t] = {}; mat[t].name = locMaterial[t]; mat[t].entries = {} end mat[t].entries[#mat[t].entries+1] = { id = p[i], color = {r,g,b} } end end self:clear() for t, m in pairs(mat) do self:append(PaletteRow(m, vl)) end UiForceFocus(self.children[1].navId) end return vl:new(obj) end PaletteWindow = Ui.Window { id = "paletteWindow", x = 30, y = 30, w = 460, h = 1020, interactable = true, modal = true, animator = {playTime = 0.25}, blur = false, first = true, mainFrameWidth = 0, lastDevice = UI_DEVICE_NONE, -- Signals signal = { onLogicUpdate = 0, onDeviceChanged = 2, onColorUnselected = 3, onPaletteButtonPressed = 4, onSpecialButtonPressed = 5, onClose = 6, }, Show = function(self, flag) self.tasks:push(function() self:show(flag) end) end, Hide = function(self, flag) self.isQuiting = false self.tasks:push(function() self:hide() end) end, Exit = function(self) self.isQuiting = true end, -- onInit = function(self) self.mainFrameWidth = UiWidth() self:inject{ "btnExit", "btnShortCut", "btnUndo", "buttonBarButtons", "paletteFrame", "background", "hintsBar" } self.currentBrushShape = -1 self:connect( self.signal.onPaletteButtonPressed, self, self.onPaletteButtonPressed) self:connect( self.signal.onSpecialButtonPressed, self, self.onSpecialButtonPressed) end, isVisible = function(self) return not self.isClosed end, activate = function(self) active = true selectedShape = 0 camera = GetPlayerCameraTransform() local eulerX, eulerY, _ = GetQuatEuler(camera.rot) cameraX, cameraY = eulerX, eulerY cameraMotion = Vec(0,0,0) local entities = Spawn("", Transform()) brush = entities[1] forceUpdateBrush = true modeChangeTimer = 0 gToolSelectAlpha = 0 toolSelect = false PaletteWindow:Hide() SetBool("game.ui.hidemods", true) end, onShow = function(self) self.paletteFrame:onShapeChanged() uiHover = false end, onClose = function(self) HintsWindow:show(active and not pick) uiHover = true self:emit(self.signal.onClose) end, onMouseEnter = function(self) uiHover = true end, onMouseLeave = function(self) uiHover = false end, onLogicUpdate = function(self) hotKey = "" local currentDevice = LastInputDevice() if currentDevice ~= self.lastDevice then self:emit(self.signal.onDeviceChanged, currentDevice) self.lastDevice = currentDevice if currentDevice == UI_DEVICE_GAMEPAD then self.isGamepad = true elseif currentDevice == UI_DEVICE_MOUSE then self.isGamepad = false end end self:emit(self.signal.onLogicUpdate) self.btnUndo.disabled = not undo.available end, onPaletteButtonPressed = function(self) for _, button in pairs({self.buttonBarButtons.foliage, self.buttonBarButtons.brick, self.buttonBarButtons.pick}) do if button.selected then button.selected = false button:setHovered(false) button.text = button.original end end end, onSpecialButtonPressed = function(self) self.paletteFrame:clearMaterial() self.paletteFrame:clearText() end, Ui.ImageBox { id = "background", path = "ui/common/box-solid-10.png", w = 460, h = 1020, border = {width = 10, height = 10}, color = {0,0,0,0.75}, visible = true, }, CMColumn { id = "PaletteVerticalLayout", Ui.VerticalLayout { id = "dummy", margin = {top = 20, left = 20}, Palette{ id = "paletteFrame", }, }, Ui.Frame { id = "buttonBar", w = 300, h = 386, margin = {top = 40}, hAlign = Ui.ContentLayout.Horizontal.Center, visible = true, CMColumn { id = "buttonBarButtons", padding = 20, specialMaterialBtn = nil, onCreate = function(self) self:inject{"foliage", "brick", "pick"} end, switchMaterial = function(self, button) if self.specialMaterialBtn ~= nil and self.specialMaterialBtn ~= button then self.specialMaterialBtn.selected = false self.specialMaterialBtn:setHovered(false) self.specialMaterialBtn.text = self.specialMaterialBtn.original end self.specialMaterialBtn = button PaletteWindow:emit(PaletteWindow.signal.onSpecialButtonPressed) end, onPreDraw = function(self) if UiIsComponentInFocus(self.navId) then if wasAnyActionPressed({"menu_right", "menu_left"}) then UiNavSkipUpdate() end end self.navId = UiNavGroupBegin() if self.w > 0 and self.h > 0 then UiNavGroupSize(self.w, self.h) end end, onPostDraw = function(self) UiNavGroupEnd() end, selectableButton { id = "foliage", text = "loc@UI_CREATIVE_FOLIAGE", on = false, Selected = function(self) materialBrush = "foliage" material = 255 brushType = "noise" forceUpdateBrush = true self.parent:switchMaterial(self) end }, selectableButton { id = "brick", text = "loc@UI_CREATIVE_BRICK", actionId = "FOLIAGE_SELECT", on = false, Selected = function(self) materialBrush = "brick" material = 255 brushType = "sphere" forceUpdateBrush = true self.parent:switchMaterial(self) end }, selectableButton { id = "pick", text = "loc@UI_CREATIVE_PICK_MATERIAL", switchable = false, selected = false, onCreate = function(self) self.parent:onCreate(self.text) pick = self.selected end, onPreDraw = function(self) if pick == false and self.selected then self:onPressed() end end, Selected = function(self,selected) pick = selected if pick then PaletteWindow:hide() end self.parent:switchMaterial(self) end }, selectableButton { text = "loc@UI_CREATIVE_GRID", switchable = false, Selected = function(self,selected) grid = selected end }, paletteButton { id = "btnUndo", text = "loc@UI_CREATIVE_UNDO", disabled = true, onPressed = function(self) if undo.available then trigUndo = true end end }, paletteButton { id = "btnShortCut", text = "loc@UI_CREATIVE_SHORTCUT", onCreate = function(self) self.shortcut = GetString("savegame.creativemode.shortcut") self.text = GetTranslatedStringByKey("UI_CREATIVE_SHORTCUT") .. ": " .. self.shortcut EventMonitor:connect( EventMonitor.signal.onLogicUpdate, self, function(self) self.visible = not EventMonitor.isGamepad end ) PaletteWindow:connect( PaletteWindow.signal.onLogicUpdate, self, self.onLogicUpdate ) PaletteWindow:connect( PaletteWindow.signal.onClose, self, function(self) registerShortCut = false end ) end, onLogicUpdate = function(self) local shortcut = GetString("savegame.creativemode.shortcut") if shortcut == "" then shortcut = GetTranslatedStringByKey("NONE") end local caption = GetTranslatedStringByKey("UI_CREATIVE_SHORTCUT") .. ": " .. shortcut self.text = caption if registerShortCut then caption = GetTranslatedStringByKey("CAPTION_PRESS_KEY") local last = InputLastPressedKey() self.text = caption if last ~= "" then self.text = GetTranslatedStringByKey("UI_CREATIVE_SHORTCUT") if last == "esc" then last = "" end SetString("savegame.creativemode.shortcut", last) registerShortCut = false end end end, onPressed = function(self) registerShortCut = true end }, paletteButton { id = "btnExit", text = "loc@UI_BUTTON_EXIT", onPressed = function(self) PaletteWindow:Exit() end }, }, }, Ui.Frame{ debug = false, w = 460, h = 42, margin = {top = -20}, hAlign = Ui.ContentLayout.Horizontal.Center, onCreate = function(self) EventMonitor:connect( EventMonitor.signal.onDeviceChanged, self, function(self, device) self.visible = device == UI_DEVICE_GAMEPAD end ) end, Ui.HorizontalLayout { debug = false, w = 460, h = 42, hAlign = Ui.ContentLayout.Horizontal.Center, onCreate = function(self) PaletteWindow:connect( PaletteWindow.signal.onDeviceChanged, self, function(self, device) self.visible = device == UI_DEVICE_GAMEPAD end ) end, Ui.HorizontalLayout { debug = false, id = "hintsVL", hAlign = Ui.ContentLayout.Horizontal.Center, vAlign = Ui.ContentLayout.Vertical.Top, padding = 20, onPreDraw = function(self) self.Select.children[1]:update() self.Back.children[1]:update() end, Ui.HorizontalLayout { id = "Select", debug = false, alignment = Ui.ContentLayout.Vertical.Middle, Ui.Text{ ico = "[[menu:menu_accept;iconsize=42,42]]", loc = Ui.loc"UI_BUTTON_SELECT", update = function(self) self.text = self.ico..self.loc:translate() end, text = "", }, }, Ui.HorizontalLayout{ id = "Back", debug = false, alignment = Ui.ContentLayout.Vertical.Middle, Ui.Text{ ico = "[[menu:menu_cancel;iconsize=42,42]]", loc = Ui.loc"UI_BUTTON_CANCEL", update = function(self) self.text = self.ico..self.loc:translate() end, text = "", }, }, }} } } }