Source code for viewports.camera

# -*- coding: utf_8 -*-
# The above line is necessary, unless we want problems with encodings...
import sys
from compass import CompassOverlay
from raycaster import TooFarException
import raycaster
import keys
import pygame

import math
import copy
import numpy
from config import config
import frustum
import logging
import glutils
import mceutils
import itertools
import pymclevel
from pymclevel import MCEDIT_DEFS, MCEDIT_IDS

from math import isnan
from datetime import datetime, timedelta

from OpenGL import GL
from OpenGL import GLU

from albow import alert, AttrRef, Button, Column, input_text, Row, TableColumn, TableView, Widget, CheckBox, \
    TextFieldWrapped, MenuButton, ChoiceButton, IntInputRow, TextInputRow, showProgress, IntField, ask
from albow.controls import Label, ValueDisplay
from albow.dialogs import Dialog, wrapped_label
from albow.openglwidgets import GLViewport
from albow.extended_widgets import BasicTextInputRow, CheckBoxLabel
from albow.translate import _
from albow.root import get_top_widget
from pygame import mouse
from depths import DepthOffset
from editortools.operation import Operation
from glutils import gl
from pymclevel.nbt import TAG_String
from editortools.nbtexplorer import SlotEditor

[docs]class SignEditOperation(Operation): def __init__(self, tool, level, tileEntity, backupTileEntity): self.tool = tool self.level = level self.tileEntity = tileEntity self.undoBackupEntityTag = backupTileEntity self.canUndo = False
[docs] def perform(self, recordUndo=True): if self.level.saving: alert("Cannot perform action while saving is taking place") return self.level.addTileEntity(self.tileEntity) self.canUndo = True
[docs] def undo(self): self.redoBackupEntityTag = copy.deepcopy(self.tileEntity) self.level.addTileEntity(self.undoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(self.tileEntity), (1, 1, 1))
[docs] def redo(self): self.level.addTileEntity(self.redoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(self.tileEntity), (1, 1, 1))
[docs]class CameraViewport(GLViewport): anchor = "tlbr" oldMousePosition = None dontShowMessageAgain = False def __init__(self, editor, def_enc=None): self.editor = editor global DEF_ENC DEF_ENC = def_enc or editor.mcedit.def_enc rect = editor.mcedit.rect GLViewport.__init__(self, rect) # Declare a pseudo showCommands function, since it is called by other objects before its creation in mouse_move. self.showCommands = lambda:None near = 0.5 far = 4000.0 self.near = near self.far = far self.brake = False self.lastTick = datetime.now() # self.nearheight = near * tang self.cameraPosition = (16., 45., 16.) self.velocity = [0., 0., 0.] self.yaw = -45. # degrees self._pitch = 0.1 self.cameraVector = self._cameraVector() # A state machine to dodge an apparent bug in pygame that generates erroneous mouse move events # 0 = bad event already happened # 1 = app just started or regained focus since last bad event # 2 = mouse cursor was hidden after state 1, next event will be bad self.avoidMouseJumpBug = 1 config.settings.drawSky.addObserver(self) config.settings.drawFog.addObserver(self) config.settings.superSecretSettings.addObserver(self) config.settings.showCeiling.addObserver(self) config.controls.cameraAccel.addObserver(self, "accelFactor") config.controls.cameraMaxSpeed.addObserver(self, "maxSpeed") config.controls.cameraBrakingSpeed.addObserver(self, "brakeMaxSpeed") config.controls.invertMousePitch.addObserver(self) config.controls.autobrake.addObserver(self) config.controls.swapAxes.addObserver(self) config.settings.compassToggle.addObserver(self) config.settings.fov.addObserver(self, "fovSetting", callback=self.updateFov) self.mouseVector = (0, 0, 0) self.root = self.get_root() self.hoveringCommandBlock = [False, ""] self.block_info_parsers = None # self.add(DebugDisplay(self, "cameraPosition", "blockFaceUnderCursor", "mouseVector", "mouse3dPoint")) @property def pitch(self): return self._pitch @pitch.setter def pitch(self, val): self._pitch = min(89.999, max(-89.999, val))
[docs] def updateFov(self, val=None): hfov = self.fovSetting fov = numpy.degrees(2.0 * numpy.arctan(self.size[0] / self.size[1] * numpy.tan(numpy.radians(hfov) * 0.5))) self.fov = fov self.tang = numpy.tan(numpy.radians(fov))
[docs] def stopMoving(self): self.velocity = [0, 0, 0]
[docs] def brakeOn(self): self.brake = True
[docs] def brakeOff(self): self.brake = False
tickInterval = 1000 / config.settings.targetFPS.get() oldPosition = (0, 0, 0) flyMode = config.settings.flyMode.property()
[docs] def tickCamera(self, frameStartTime, inputs, inSpace): timePassed = (frameStartTime - self.lastTick).microseconds if timePassed <= self.tickInterval * 1000 or not pygame.key.get_focused(): return self.lastTick = frameStartTime timeDelta = float(timePassed) / 1000000. timeDelta = min(timeDelta, 0.125) # 8fps lower limit! drag = config.controls.cameraDrag.get() accel_factor = drag + config.controls.cameraAccel.get() # if we're in space, move faster drag_epsilon = 10.0 * timeDelta if self.brake: max_speed = self.brakeMaxSpeed else: max_speed = self.maxSpeed if inSpace or self.root.sprint: accel_factor *= 3.0 max_speed *= 3.0 self.root.sprint = False elif config.settings.viewMode.get() == "Chunk": accel_factor *= 2.0 max_speed *= 2.0 pi = self.editor.cameraPanKeys mouseSpeed = config.controls.mouseSpeed.get() self.yaw += pi[0] * mouseSpeed self.pitch += pi[1] * mouseSpeed if config.settings.viewMode.get() == "Chunk": (dx, dy, dz) = (0, -0.25, -1) self.yaw = -180 self.pitch = 10 elif self.flyMode: (dx, dy, dz) = self._anglesToVector(self.yaw, 0) elif self.swapAxes: p = self.pitch if p > 80: p = 0 (dx, dy, dz) = self._anglesToVector(self.yaw, p) else: (dx, dy, dz) = self._cameraVector() velocity = self.velocity # xxx learn to use matrix/vector libs i = inputs yaw = numpy.radians(self.yaw) cosyaw = -numpy.cos(yaw) sinyaw = numpy.sin(yaw) directedInputs = mceutils.normalize(( i[0] * cosyaw + i[2] * dx, i[1] + i[2] * dy, i[2] * dz - i[0] * sinyaw, )) # give the camera an impulse according to the state of the inputs and in the direction of the camera cameraAccel = map(lambda x: x * accel_factor * timeDelta, directedInputs) # cameraImpulse = map(lambda x: x*impulse_factor, directedInputs) newVelocity = map(lambda a, b: a + b, velocity, cameraAccel) velocityDir, speed = mceutils.normalize_size(newVelocity) # apply drag if speed: if self.autobrake and not any(inputs): speed *= 0.15 else: sign = speed / abs(speed) speed = abs(speed) speed = speed - (drag * timeDelta) if speed < 0.0: speed = 0.0 speed *= sign speed = max(-max_speed, min(max_speed, speed)) if abs(speed) < drag_epsilon: speed = 0 velocity = map(lambda a: a * speed, velocityDir) # velocity = map(lambda p,d: p + d, velocity, cameraImpulse) d = map(lambda a, b: abs(a - b), self.cameraPosition, self.oldPosition) if d[0] + d[2] > 32.0: self.oldPosition = self.cameraPosition self.updateFloorQuad() self.cameraPosition = map(lambda p, d: p + d * timeDelta, self.cameraPosition, velocity) if self.cameraPosition[1] > 3800.: self.cameraPosition[1] = 3800. elif self.cameraPosition[1] < -1000.: self.cameraPosition[1] = -1000. self.velocity = velocity self.cameraVector = self._cameraVector() self.editor.renderer.position = self.cameraPosition if self.editor.currentTool.previewRenderer: self.editor.currentTool.previewRenderer.position = self.cameraPosition
[docs] def setModelview(self): pos = self.cameraPosition look = numpy.array(self.cameraPosition) look = look.astype(float) + self.cameraVector up = (0, 1, 0) GLU.gluLookAt(pos[0], pos[1], pos[2], look[0], look[1], look[2], up[0], up[1], up[2])
def _cameraVector(self): return self._anglesToVector(self.yaw, self.pitch) @staticmethod def _anglesToVector(yaw, pitch): def nanzero(x): if isnan(x): return 0 else: return x dx = -math.sin(math.radians(yaw)) * math.cos(math.radians(pitch)) dy = -math.sin(math.radians(pitch)) dz = math.cos(math.radians(yaw)) * math.cos(math.radians(pitch)) return map(nanzero, [dx, dy, dz])
[docs] def updateMouseVector(self): self.mouseVector = self._mouseVector()
def _mouseVector(self): """ returns a vector reflecting a ray cast from the camera position to the mouse position on the near plane """ x, y = mouse.get_pos() # if (x, y) not in self.rect: # return (0, 0, 0); # xxx y = self.root.height - y point1 = unproject(x, y, 0.0) point2 = unproject(x, y, 1.0) v = numpy.array(point2) - point1 v = mceutils.normalize(v) return v def _blockUnderCursor(self, center=False): """ returns a point in 3d space that was determined by reading the depth buffer value """ try: GL.glReadBuffer(GL.GL_BACK) except Exception: logging.exception('Exception during glReadBuffer') ws = self.root.size if center: x, y = ws x //= 2 y //= 2 else: x, y = mouse.get_pos() if (x < 0 or y < 0 or x >= ws[0] or y >= ws[1]): return 0, 0, 0 y = ws[1] - y try: pixel = GL.glReadPixels(x, y, 1, 1, GL.GL_DEPTH_COMPONENT, GL.GL_FLOAT) newpoint = unproject(x, y, pixel[0]) except Exception: return 0, 0, 0 return newpoint
[docs] def updateBlockFaceUnderCursor(self): focusPair = None if not self.enableMouseLag or self.editor.frames & 1: self.updateMouseVector() if self.editor.mouseEntered: if not self.mouseMovesCamera: try: focusPair = raycaster.firstBlock(self.cameraPosition, self._mouseVector(), self.editor.level, 100, config.settings.viewMode.get()) except TooFarException: mouse3dPoint = self._blockUnderCursor() focusPair = self._findBlockFaceUnderCursor(mouse3dPoint) elif self.editor.longDistanceMode: mouse3dPoint = self._blockUnderCursor(True) focusPair = self._findBlockFaceUnderCursor(mouse3dPoint) # otherwise, find the block at a controllable distance in front of the camera if focusPair is None: if self.blockFaceUnderCursor is None or self.mouseMovesCamera: focusPair = (self.getCameraPoint(), (0, 0, 0)) else: focusPair = self.blockFaceUnderCursor try: if focusPair[0] is not None and self.editor.level.tileEntityAt(*focusPair[0]): changed = False te = self.editor.level.tileEntityAt(*focusPair[0]) backupTE = copy.deepcopy(te) if te["id"].value == "Sign" or MCEDIT_IDS.GET(e["id"].value) in ("DEF_BLOCKS_STANDING_SIGN", "DEFS_BLOCKS_WALL_SIGN"): if "Text1" in te and "Text2" in te and "Text3" in te and "Text4" in te: for i in xrange(1,5): if len(te["Text"+str(i)].value) > 32767: te["Text"+str(i)] = pymclevel.TAG_String(str(te["Text"+str(i)].value)[:32767]) changed = True if changed: response = None if not self.dontShowMessageAgain: response = ask("Found a sign that exceeded the maximum character limit. Automatically trimmed the sign to prevent crashes.", responses=["Ok", "Don't show this again"]) if response is not None and response == "Don't show this again": self.dontShowMessageAgain = True op = SignEditOperation(self.editor, self.editor.level, te, backupTE) self.editor.addOperation(op) if op.canUndo: self.editor.addUnsavedEdit() except: pass self.blockFaceUnderCursor = focusPair
def _findBlockFaceUnderCursor(self, projectedPoint): """Returns a (pos, Face) pair or None if one couldn't be found""" d = [0, 0, 0] try: intProjectedPoint = map(int, map(numpy.floor, projectedPoint)) except ValueError: return None # catch NaNs intProjectedPoint[1] = max(-1, intProjectedPoint[1]) # find out which face is under the cursor. xxx do it more precisely faceVector = ((projectedPoint[0] - (intProjectedPoint[0] + 0.5)), (projectedPoint[1] - (intProjectedPoint[1] + 0.5)), (projectedPoint[2] - (intProjectedPoint[2] + 0.5)) ) av = map(abs, faceVector) i = av.index(max(av)) delta = faceVector[i] if delta < 0: d[i] = -1 else: d[i] = 1 potentialOffsets = [] try: block = self.editor.level.blockAt(*intProjectedPoint) except (EnvironmentError, pymclevel.ChunkNotPresent): return intProjectedPoint, d if block == pymclevel.alphaMaterials.SnowLayer.ID: potentialOffsets.append((0, 1, 0)) else: # discard any faces that aren't likely to be exposed for face, offsets in pymclevel.faceDirections: point = map(lambda a, b: a + b, intProjectedPoint, offsets) try: neighborBlock = self.editor.level.blockAt(*point) if block != neighborBlock: potentialOffsets.append(offsets) except (EnvironmentError, pymclevel.ChunkNotPresent): pass # check each component of the face vector to see if that face is exposed if tuple(d) not in potentialOffsets: av[i] = 0 i = av.index(max(av)) d = [0, 0, 0] delta = faceVector[i] if delta < 0: d[i] = -1 else: d[i] = 1 if tuple(d) not in potentialOffsets: av[i] = 0 i = av.index(max(av)) d = [0, 0, 0] delta = faceVector[i] if delta < 0: d[i] = -1 else: d[i] = 1 if tuple(d) not in potentialOffsets: if len(potentialOffsets): d = potentialOffsets[0] else: # use the top face as a fallback d = [0, 1, 0] return intProjectedPoint, d @property def ratio(self): return self.width / float(self.height) startingMousePosition = None
[docs] def mouseLookOn(self): self.root.capture_mouse(self) self.focus_switch = None self.startingMousePosition = mouse.get_pos() if self.avoidMouseJumpBug == 1: self.avoidMouseJumpBug = 2
[docs] def mouseLookOff(self): self.root.capture_mouse(None) if self.startingMousePosition: mouse.set_pos(*self.startingMousePosition) self.startingMousePosition = None
@property def mouseMovesCamera(self): return self.root.captured_widget is not None
[docs] def toggleMouseLook(self): if not self.mouseMovesCamera: self.mouseLookOn() else: self.mouseLookOff() # mobs is overrided in __init__
mobs = pymclevel.Entity.monsters + ["[Custom]"] @mceutils.alertException def editMonsterSpawner(self, point): mobs = self.mobs _mobs = {} # Get the mobs from the versionned data from pymclevel import MCEDIT_DEFS, MCEDIT_IDS if MCEDIT_DEFS.get('spawner_monsters'): mobs = [] for a in MCEDIT_DEFS['spawner_monsters']: _id = MCEDIT_IDS[a] name = _(MCEDIT_DEFS[_id]['name']) _mobs[name] = a _mobs[a] = name mobs.append(name) tileEntity = self.editor.level.tileEntityAt(*point) undoBackupEntityTag = copy.deepcopy(tileEntity) if not tileEntity: tileEntity = pymclevel.TAG_Compound() tileEntity["id"] = pymclevel.TAG_String(MCEDIT_DEFS.get("MobSpawner", "MobSpawner")) tileEntity["x"] = pymclevel.TAG_Int(point[0]) tileEntity["y"] = pymclevel.TAG_Int(point[1]) tileEntity["z"] = pymclevel.TAG_Int(point[2]) tileEntity["Delay"] = pymclevel.TAG_Short(120) tileEntity["EntityId"] = pymclevel.TAG_String(MCEDIT_DEFS.get(mobs[0], mobs[0])) self.editor.level.addTileEntity(tileEntity) panel = Dialog() def addMob(id): if id not in mobs: mobs.insert(0, id) mobTable.selectedIndex = 0 def selectTableRow(i, evt): if mobs[i] == "[Custom]": id = input_text("Type in an EntityID for this spawner. Invalid IDs may crash Minecraft.", 150) if id: addMob(id) else: return mobTable.selectedIndex = mobs.index(id) else: mobTable.selectedIndex = i if evt.num_clicks == 2: panel.dismiss() mobTable = TableView(columns=( TableColumn("", 200), ) ) mobTable.num_rows = lambda: len(mobs) mobTable.row_data = lambda i: (mobs[i],) mobTable.row_is_selected = lambda x: x == mobTable.selectedIndex mobTable.click_row = selectTableRow mobTable.selectedIndex = 0 def selectedMob(): val = mobs[mobTable.selectedIndex] return _mobs.get(val, val) def cancel(): mobs[mobTable.selectedIndex] = id panel.dismiss() if "EntityId" in tileEntity: _id = tileEntity["EntityId"].value # id = MCEDIT_DEFS.get(MCEDIT_IDS.get(_id, _id), {}).get("name", _id) elif "SpawnData" in tileEntity: _id = tileEntity["SpawnData"]["id"].value else: _id = "[Custom]" id = MCEDIT_DEFS.get(MCEDIT_IDS.get(_id, _id), {}).get("name", _id) addMob(id) mobTable.selectedIndex = mobs.index(id) oldChoiceCol = Column((Label(_("Current: ") + _mobs.get(id, id), align='l', width=200), )) newChoiceCol = Column((ValueDisplay(width=200, get_value=lambda: _("Change to: ") + selectedMob()), mobTable)) lastRow = Row((Button("OK", action=panel.dismiss), Button("Cancel", action=cancel))) panel.add(Column((oldChoiceCol, newChoiceCol, lastRow))) panel.shrink_wrap() panel.present() class MonsterSpawnerEditOperation(Operation): def __init__(self, tool, level): self.tool = tool self.level = level self.undoBackupEntityTag = undoBackupEntityTag self.canUndo = False def perform(self, recordUndo=True): if self.level.saving: alert("Cannot perform action while saving is taking place") return self.level.addTileEntity(tileEntity) self.canUndo = True def undo(self): self.redoBackupEntityTag = copy.deepcopy(tileEntity) self.level.addTileEntity(self.undoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntity), (1, 1, 1)) def redo(self): self.level.addTileEntity(self.redoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntity), (1, 1, 1)) if id != selectedMob(): if "EntityId" in tileEntity: tileEntity["EntityId"] = pymclevel.TAG_String(selectedMob()) if "SpawnData" in tileEntity: # Try to not clear the spawn data, but only update the mob id # tileEntity["SpawnData"] = pymclevel.TAG_Compound() tag_id = pymclevel.TAG_String(selectedMob()) if "id" in tileEntity["SpawnData"]: tag_id.name = "id" tileEntity["SpawnData"]["id"] = tag_id if "EntityId" in tileEntity["SpawnData"]: tileEntity["SpawnData"]["EntityId"] = tag_id if "SpawnPotentials" in tileEntity: for potential in tileEntity["SpawnPotentials"]: if "Entity" in potential: # MC 1.9+ if potential["Entity"]["id"].value == id or ("EntityId" in potential["Entity"] and potential["Entity"]["EntityId"].value == id): potential["Entity"] = pymclevel.TAG_Compound() potential["Entity"]["id"] = pymclevel.TAG_String(selectedMob()) elif "Properties" in potential: # MC before 1.9 if "Type" in potential and potential["Type"].value == id: potential["Type"] = pymclevel.TAG_String(selectedMob()) # We also can change some other values in the Properties tag, but it is useless in MC 1.8+. # The fact is this data will not be updated by the game after the mob type is changed, but the old mob will not spawn. # put_entityid = False # put_id = False # if "EntityId" in potential["Properties"] and potential["Properties"]["EntityId"].value == id: # put_entityid = True # if "id" in potential["Properties"] and potential["Properties"]["id"].value == id: # put_id = True # new_props = pymclevel.TAG_Compound() # if put_entityid: # new_props["EntityId"] = pymclevel.TAG_String(selectedMob()) # if put_id: # new_props["id"] = pymclevel.TAG_String(selectedMob()) # potential["Properties"] = new_props op = MonsterSpawnerEditOperation(self.editor, self.editor.level) self.editor.addOperation(op) if op.canUndo: self.editor.addUnsavedEdit() @mceutils.alertException def editJukebox(self, point): discs = { "[No Record]": None, "13": 2256, "cat": 2257, "blocks": 2258, "chirp": 2259, "far": 2260, "mall": 2261, "mellohi": 2262, "stal": 2263, "strad": 2264, "ward": 2265, "11": 2266, "wait": 2267 } tileEntity = self.editor.level.tileEntityAt(*point) undoBackupEntityTag = copy.deepcopy(tileEntity) if not tileEntity: tileEntity = pymclevel.TAG_Compound() tileEntity["id"] = pymclevel.TAG_String("RecordPlayer") tileEntity["x"] = pymclevel.TAG_Int(point[0]) tileEntity["y"] = pymclevel.TAG_Int(point[1]) tileEntity["z"] = pymclevel.TAG_Int(point[2]) self.editor.level.addTileEntity(tileEntity) panel = Dialog() def selectTableRow(i, evt): discTable.selectedIndex = i if evt.num_clicks == 2: panel.dismiss() discTable = TableView(columns=( TableColumn("", 200), ) ) discTable.num_rows = lambda: len(discs) discTable.row_data = lambda i: (selectedDisc(i),) discTable.row_is_selected = lambda x: x == discTable.selectedIndex discTable.click_row = selectTableRow discTable.selectedIndex = 0 def selectedDisc(id): if id == 0: return "[No Record]" return discs.keys()[discs.values().index(id + 2255)] def cancel(): if id == "[No Record]": discTable.selectedIndex = 0 else: discTable.selectedIndex = discs[id] - 2255 panel.dismiss() if "RecordItem" in tileEntity: if tileEntity["RecordItem"]["id"].value == "minecraft:air": id = "[No Record]" else: id = tileEntity["RecordItem"]["id"].value[17:] elif "Record" in tileEntity: if tileEntity["Record"].value == 0: id = "[No Record]" else: id = selectedDisc(tileEntity["Record"].value - 2255) else: id = "[No Record]" if id == "[No Record]": discTable.selectedIndex = 0 else: discTable.selectedIndex = discs[id] - 2255 oldChoiceCol = Column((Label(_("Current: ") + id, align='l', width=200), )) newChoiceCol = Column((ValueDisplay(width=200, get_value=lambda: _("Change to: ") + selectedDisc(discTable.selectedIndex)), discTable)) lastRow = Row((Button("OK", action=panel.dismiss), Button("Cancel", action=cancel))) panel.add(Column((oldChoiceCol, newChoiceCol, lastRow))) panel.shrink_wrap() panel.present() class JukeboxEditOperation(Operation): def __init__(self, tool, level): self.tool = tool self.level = level self.undoBackupEntityTag = undoBackupEntityTag self.canUndo = False def perform(self, recordUndo=True): if self.level.saving: alert("Cannot perform action while saving is taking place") return self.level.addTileEntity(tileEntity) self.canUndo = True def undo(self): self.redoBackupEntityTag = copy.deepcopy(tileEntity) self.level.addTileEntity(self.undoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntity), (1, 1, 1)) def redo(self): self.level.addTileEntity(self.redoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntity), (1, 1, 1)) if id != selectedDisc(discTable.selectedIndex): if "RecordItem" in tileEntity: del tileEntity["RecordItem"] if discTable.selectedIndex == 0: tileEntity["Record"] = pymclevel.TAG_Int(0) self.editor.level.setBlockDataAt(tileEntity["x"].value, tileEntity["y"].value, tileEntity["z"].value, 0) else: tileEntity["Record"] = pymclevel.TAG_Int(discTable.selectedIndex + 2255) self.editor.level.setBlockDataAt(tileEntity["x"].value, tileEntity["y"].value, tileEntity["z"].value, 1) op = JukeboxEditOperation(self.editor, self.editor.level) self.editor.addOperation(op) if op.canUndo: self.editor.addUnsavedEdit() @mceutils.alertException def editNoteBlock(self, point): notes = [ "F# (0.5)", "G (0.53)", "G# (0.56)", "A (0.6)", "A# (0.63)", "B (0.67)", "C (0.7)", "C# (0.75)", "D (0.8)", "D# (0.85)", "E (0.9)", "F (0.95)", "F# (1.0)", "G (1.05)", "G# (1.1)", "A (1.2)", "A# (1.25)", "B (1.32)", "C (1.4)", "C# (1.5)", "D (1.6)", "D# (1.7)", "E (1.8)", "F (1.9)", "F# (2.0)" ] tileEntity = self.editor.level.tileEntityAt(*point) undoBackupEntityTag = copy.deepcopy(tileEntity) if not tileEntity: tileEntity = pymclevel.TAG_Compound() tileEntity["id"] = pymclevel.TAG_String(MCEDIT_DEFS.get("MobSpawner", "MobSpawner")) tileEntity["x"] = pymclevel.TAG_Int(point[0]) tileEntity["y"] = pymclevel.TAG_Int(point[1]) tileEntity["z"] = pymclevel.TAG_Int(point[2]) tileEntity["note"] = pymclevel.TAG_Byte(0) self.editor.level.addTileEntity(tileEntity) panel = Dialog() def selectTableRow(i, evt): noteTable.selectedIndex = i if evt.num_clicks == 2: panel.dismiss() noteTable = TableView(columns=( TableColumn("", 200), ) ) noteTable.num_rows = lambda: len(notes) noteTable.row_data = lambda i: (notes[i],) noteTable.row_is_selected = lambda x: x == noteTable.selectedIndex noteTable.click_row = selectTableRow noteTable.selectedIndex = 0 def selectedNote(): return notes[noteTable.selectedIndex] def cancel(): noteTable.selectedIndex = id panel.dismiss() id = tileEntity["note"].value noteTable.selectedIndex = id oldChoiceCol = Column((Label(_("Current: ") + notes[id], align='l', width=200), )) newChoiceCol = Column((ValueDisplay(width=200, get_value=lambda: _("Change to: ") + selectedNote()), noteTable)) lastRow = Row((Button("OK", action=panel.dismiss), Button("Cancel", action=cancel))) panel.add(Column((oldChoiceCol, newChoiceCol, lastRow))) panel.shrink_wrap() panel.present() class NoteBlockEditOperation(Operation): def __init__(self, tool, level): self.tool = tool self.level = level self.undoBackupEntityTag = undoBackupEntityTag self.canUndo = False def perform(self, recordUndo=True): if self.level.saving: alert("Cannot perform action while saving is taking place") return self.level.addTileEntity(tileEntity) self.canUndo = True def undo(self): self.redoBackupEntityTag = copy.deepcopy(tileEntity) self.level.addTileEntity(self.undoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntity), (1, 1, 1)) def redo(self): self.level.addTileEntity(self.redoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntity), (1, 1, 1)) if id != noteTable.selectedIndex: tileEntity["note"] = pymclevel.TAG_Byte(noteTable.selectedIndex) op = NoteBlockEditOperation(self.editor, self.editor.level) self.editor.addOperation(op) if op.canUndo: self.editor.addUnsavedEdit() @mceutils.alertException def editSign(self, point): tileEntity = self.editor.level.tileEntityAt(*point) undoBackupEntityTag = copy.deepcopy(tileEntity) linekeys = ["Text" + str(i) for i in range(1, 5)] # From version 1.8, signs accept Json format. # 1.9 does no more support the old raw string fomat. splitVersion = self.editor.level.gameVersion.split('.') newFmtVersion = ['1','9'] fmt = "" json_fmt = False f = lambda a,b: (a + (['0'] * max(len(b) - len(a), 0)), b + (['0'] * max(len(a) - len(b), 0))) if False not in map(lambda x,y: (int(x) if x.isdigit() else x) >= (int(y) if y.isdigit() else y),*f(splitVersion, newFmtVersion))[:2]: json_fmt = True fmt = '{"text":""}' if not tileEntity: tileEntity = pymclevel.TAG_Compound() # Don't know how to handle the difference between wall and standing signs for now... # Just let this like it is until we can find the way! tileEntity["id"] = pymclevel.TAG_String("Sign") tileEntity["x"] = pymclevel.TAG_Int(point[0]) tileEntity["y"] = pymclevel.TAG_Int(point[1]) tileEntity["z"] = pymclevel.TAG_Int(point[2]) for l in linekeys: tileEntity[l] = pymclevel.TAG_String(fmt) self.editor.level.addTileEntity(tileEntity) panel = Dialog() lineFields = [TextFieldWrapped(width=400) for l in linekeys] for l, f in zip(linekeys, lineFields): f.value = tileEntity[l].value # Double quotes handling for olf sign text format. if f.value == 'null': f.value = fmt elif json_fmt and f.value == '': f.value = fmt else: if f.value.startswith('"') and f.value.endswith('"'): f.value = f.value[1:-1] if '\\"' in f.value: f.value = f.value.replace('\\"', '"') colors = [ u"§0 Black", u"§1 Dark Blue", u"§2 Dark Green", u"§3 Dark Aqua", u"§4 Dark Red", u"§5 Dark Purple", u"§6 Gold", u"§7 Gray", u"§8 Dark Gray", u"§9 Blue", u"§a Green", u"§b Aqua", u"§c Red", u"§d Light Purple", u"§e Yellow", u"§f White", ] def menu_picked(index): c = u"§%d"%index currentField = panel.focus_switch.focus_switch currentField.text += c # xxx view hierarchy currentField.insertion_point = len(currentField.text) def changeSign(): unsavedChanges = False fmt = '"{}"' u_fmt = u'"%s"' if json_fmt: fmt = '{}' u_fmt = u'%s' for l, f in zip(linekeys, lineFields): oldText = fmt.format(tileEntity[l]) tileEntity[l] = pymclevel.TAG_String(u_fmt%f.value[:255]) if fmt.format(tileEntity[l]) != oldText and not unsavedChanges: unsavedChanges = True if unsavedChanges: op = SignEditOperation(self.editor, self.editor.level, tileEntity, undoBackupEntityTag) self.editor.addOperation(op) if op.canUndo: self.editor.addUnsavedEdit() panel.dismiss() colorMenu = MenuButton("Add Color Code...", colors, menu_picked=menu_picked) row = Row((Button("OK", action=changeSign), Button("Cancel", action=panel.dismiss))) column = [Label("Edit Sign")] + lineFields + [colorMenu, row] panel.add(Column(column)) panel.shrink_wrap() panel.present() @mceutils.alertException def editSkull(self, point): tileEntity = self.editor.level.tileEntityAt(*point) undoBackupEntityTag = copy.deepcopy(tileEntity) skullTypes = { "Skeleton": 0, "Wither Skeleton": 1, "Zombie": 2, "Player": 3, "Creeper": 4, } inverseSkullType = { 0: "Skeleton", 1: "Wither Skeleton", 2: "Zombie", 3: "Player", 4: "Creeper", } if not tileEntity: tileEntity = pymclevel.TAG_Compound() # Don't know how to handle the difference between skulls in this context signs for now... # Tests nedded! tileEntity["id"] = pymclevel.TAG_String("Skull") tileEntity["x"] = pymclevel.TAG_Int(point[0]) tileEntity["y"] = pymclevel.TAG_Int(point[1]) tileEntity["z"] = pymclevel.TAG_Int(point[2]) tileEntity["SkullType"] = pymclevel.TAG_Byte(3) self.editor.level.addTileEntity(tileEntity) titleLabel = Label("Edit Skull Data") usernameField = TextFieldWrapped(width=150) panel = Dialog() skullMenu = ChoiceButton(map(str, skullTypes)) if "Owner" in tileEntity: usernameField.value = str(tileEntity["Owner"]["Name"].value) elif "ExtraType" in tileEntity: usernameField.value = str(tileEntity["ExtraType"].value) else: usernameField.value = "" oldUserName = usernameField.value skullMenu.selectedChoice = inverseSkullType[tileEntity["SkullType"].value] oldSelectedSkull = skullMenu.selectedChoice class SkullEditOperation(Operation): def __init__(self, tool, level): self.tool = tool self.level = level self.undoBackupEntityTag = undoBackupEntityTag self.canUndo = False def perform(self, recordUndo=True): if self.level.saving: alert("Cannot perform action while saving is taking place") return self.level.addTileEntity(tileEntity) self.canUndo = True def undo(self): self.redoBackupEntityTag = copy.deepcopy(tileEntity) self.level.addTileEntity(self.undoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntity), (1, 1, 1)) def redo(self): self.level.addTileEntity(self.redoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntity), (1, 1, 1)) def updateSkull(): if usernameField.value != oldUserName or oldSelectedSkull != skullMenu.selectedChoice: tileEntity["ExtraType"] = pymclevel.TAG_String(usernameField.value) tileEntity["SkullType"] = pymclevel.TAG_Byte(skullTypes[skullMenu.selectedChoice]) if "Owner" in tileEntity: del tileEntity["Owner"] op = SkullEditOperation(self.editor, self.editor.level) self.editor.addOperation(op) if op.canUndo: self.editor.addUnsavedEdit() chunk = self.editor.level.getChunk(int(int(point[0]) / 16), int(int(point[2]) / 16)) chunk.dirty = True panel.dismiss() okBTN = Button("OK", action=updateSkull) cancel = Button("Cancel", action=panel.dismiss) column = [titleLabel, usernameField, skullMenu, okBTN, cancel] panel.add(Column(column)) panel.shrink_wrap() panel.present() @mceutils.alertException def editCommandBlock(self, point): panel = Dialog() tileEntity = self.editor.level.tileEntityAt(*point) undoBackupEntityTag = copy.deepcopy(tileEntity) if not tileEntity: tileEntity = pymclevel.TAG_Compound() tileEntity["id"] = pymclevel.TAG_String(MCEDIT_DEFS.get("Control", "Control")) tileEntity["x"] = pymclevel.TAG_Int(point[0]) tileEntity["y"] = pymclevel.TAG_Int(point[1]) tileEntity["z"] = pymclevel.TAG_Int(point[2]) tileEntity["Command"] = pymclevel.TAG_String() tileEntity["CustomName"] = pymclevel.TAG_String("@") tileEntity["TrackOutput"] = pymclevel.TAG_Byte(0) tileEntity["SuccessCount"] = pymclevel.TAG_Int(0) self.editor.level.addTileEntity(tileEntity) titleLabel = Label("Edit Command Block") commandField = TextFieldWrapped(width=650) nameField = TextFieldWrapped(width=200) successField = IntInputRow("SuccessCount", min=0, max=15) trackOutput = CheckBox() # Fix for the '§ is ħ' issue # try: # commandField.value = tileEntity["Command"].value.decode("unicode-escape") # except: # commandField.value = tileEntity["Command"].value commandField.value = tileEntity["Command"].value oldCommand = commandField.value trackOutput.value = tileEntity["TrackOutput"].value oldTrackOutput = trackOutput.value nameField.value = tileEntity.get("CustomName", TAG_String("@")).value oldNameField = nameField.value successField.subwidgets[1].value = tileEntity.get("SuccessCount", pymclevel.TAG_Int(0)).value oldSuccess = successField.subwidgets[1].value class CommandBlockEditOperation(Operation): def __init__(self, tool, level): self.tool = tool self.level = level self.undoBackupEntityTag = undoBackupEntityTag self.canUndo = False def perform(self, recordUndo=True): if self.level.saving: alert("Cannot perform action while saving is taking place") return self.level.addTileEntity(tileEntity) self.canUndo = True def undo(self): self.redoBackupEntityTag = copy.deepcopy(tileEntity) self.level.addTileEntity(self.undoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntity), (1, 1, 1)) def redo(self): self.level.addTileEntity(self.redoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntity), (1, 1, 1)) def updateCommandBlock(): if oldCommand != commandField.value or oldTrackOutput != trackOutput.value or oldNameField != nameField.value or oldSuccess != successField.subwidgets[1].value: tileEntity["Command"] = pymclevel.TAG_String(commandField.value) tileEntity["TrackOutput"] = pymclevel.TAG_Byte(trackOutput.value) tileEntity["CustomName"] = pymclevel.TAG_String(nameField.value) tileEntity["SuccessCount"] = pymclevel.TAG_Int(successField.subwidgets[1].value) op = CommandBlockEditOperation(self.editor, self.editor.level) self.editor.addOperation(op) if op.canUndo: self.editor.addUnsavedEdit() chunk = self.editor.level.getChunk(int(int(point[0]) / 16), int(int(point[2]) / 16)) chunk.dirty = True panel.dismiss() okBTN = Button("OK", action=updateCommandBlock) cancel = Button("Cancel", action=panel.dismiss) column = [titleLabel, Label("Command:"), commandField, Row((Label("Custom Name:"), nameField)), successField, Row((Label("Track Output"), trackOutput)), okBTN, cancel] panel.add(Column(column)) panel.shrink_wrap() panel.present() return @mceutils.alertException def editContainer(self, point, containerID): tileEntityTag = self.editor.level.tileEntityAt(*point) if tileEntityTag is None: tileEntityTag = pymclevel.TileEntity.Create(containerID) pymclevel.TileEntity.setpos(tileEntityTag, point) self.editor.level.addTileEntity(tileEntityTag) if tileEntityTag["id"].value != containerID: return undoBackupEntityTag = copy.deepcopy(tileEntityTag) def itemProp(key): # xxx do validation here def getter(self): if 0 == len(tileEntityTag["Items"]): return 0 return tileEntityTag["Items"][self.selectedItemIndex][key].value def setter(self, val): if 0 == len(tileEntityTag["Items"]): return self.dirty = True tileEntityTag["Items"][self.selectedItemIndex][key].value = val return property(getter, setter) class ChestWidget(Widget): dirty = False Slot = itemProp("Slot") id = itemProp("id") Damage = itemProp("Damage") Count = itemProp("Count") itemLimit = pymclevel.TileEntity.maxItems.get(containerID, 26) def slotFormat(slot): slotNames = pymclevel.TileEntity.slotNames.get(containerID) if slotNames: return slotNames.get(slot, slot) return slot chestWidget = ChestWidget() chestItemTable = TableView(columns=[ TableColumn("Slot", 60, "l", fmt=slotFormat), TableColumn("ID / ID Name", 345, "l"), TableColumn("DMG", 50, "l"), TableColumn("Count", 65, "l"), TableColumn("Name", 260, "l"), ]) def itemName(id, damage): try: return pymclevel.items.items.findItem(id, damage).name except pymclevel.items.ItemNotFound: return "Unknown Item" def getRowData(i): item = tileEntityTag["Items"][i] slot, id, damage, count = item["Slot"].value, item["id"].value, item["Damage"].value, item["Count"].value return slot, id, damage, count, itemName(id, damage) chestWidget.selectedItemIndex = 0 def selectTableRow(i, evt): chestWidget.selectedItemIndex = i # Disabling the item selector for now, since we need PE items resources. # if evt.num_clicks > 1: # selectButtonAction() def changeValue(data): s, i, c, d = data s = int(s) s_idx = 0 chestWidget.Slot = s chestWidget.id = i chestWidget.Count = int(c) chestWidget.Damage = int(d) chestItemTable.num_rows = lambda: len(tileEntityTag["Items"]) chestItemTable.row_data = getRowData chestItemTable.row_is_selected = lambda x: x == chestWidget.selectedItemIndex chestItemTable.click_row = selectTableRow chestItemTable.change_value = changeValue def selectButtonAction(): SlotEditor(chestItemTable, (chestWidget.Slot, chestWidget.id or u"", chestWidget.Count, chestWidget.Damage) ).present() maxSlot = pymclevel.TileEntity.maxItems.get(tileEntityTag["id"].value, 27) - 1 fieldRow = ( IntInputRow("Slot: ", ref=AttrRef(chestWidget, 'Slot'), min=0, max=maxSlot), BasicTextInputRow("ID / ID Name: ", ref=AttrRef(chestWidget, 'id'), width=300), # Text to allow the input of internal item names IntInputRow("DMG: ", ref=AttrRef(chestWidget, 'Damage'), min=0, max=32767), IntInputRow("Count: ", ref=AttrRef(chestWidget, 'Count'), min=-1, max=64), # This button is inactive for now, because we need to work with different IDs types: # * The 'human' IDs: Stone, Glass, Swords... # * The MC ones: minecraft:stone, minecraft:air... # * The PE ones: 0:0, 1:0... # Button("Select", action=selectButtonAction) ) def deleteFromWorld(): i = chestWidget.selectedItemIndex item = tileEntityTag["Items"][i] id = item["id"].value Damage = item["Damage"].value deleteSameDamage = CheckBoxLabel("Only delete items with the same damage value") deleteBlocksToo = CheckBoxLabel("Also delete blocks placed in the world") if id not in (8, 9, 10, 11): # fluid blocks deleteBlocksToo.value = True w = wrapped_label( "WARNING: You are about to modify the entire world. This cannot be undone. Really delete all copies of this item from all land, chests, furnaces, dispensers, dropped items, item-containing tiles, and player inventories in this world?", 60) col = (w, deleteSameDamage) if id < 256: col += (deleteBlocksToo,) d = Dialog(Column(col), ["OK", "Cancel"]) if d.present() == "OK": def deleteItemsIter(): i = 0 if deleteSameDamage.value: def matches(t): return t["id"].value == id and t["Damage"].value == Damage else: def matches(t): return t["id"].value == id def matches_itementity(e): if e["id"].value != "Item": return False if "Item" not in e: return False t = e["Item"] return matches(t) for player in self.editor.level.players: tag = self.editor.level.getPlayerTag(player) tag["Inventory"].value = [t for t in tag["Inventory"].value if not matches(t)] for chunk in self.editor.level.getChunks(): if id < 256 and deleteBlocksToo.value: matchingBlocks = chunk.Blocks == id if deleteSameDamage.value: matchingBlocks &= chunk.Data == Damage if any(matchingBlocks): chunk.Blocks[matchingBlocks] = 0 chunk.Data[matchingBlocks] = 0 chunk.chunkChanged() self.editor.invalidateChunks([chunk.chunkPosition]) for te in chunk.TileEntities: if "Items" in te: l = len(te["Items"]) te["Items"].value = [t for t in te["Items"].value if not matches(t)] if l != len(te["Items"]): chunk.dirty = True entities = [e for e in chunk.Entities if matches_itementity(e)] if len(entities) != len(chunk.Entities): chunk.Entities.value = entities chunk.dirty = True yield (i, self.editor.level.chunkCount) i += 1 progressInfo = _("Deleting the item {0} from the entire world ({1} chunks)").format( itemName(chestWidget.id, 0), self.editor.level.chunkCount) showProgress(progressInfo, deleteItemsIter(), cancel=True) self.editor.addUnsavedEdit() chestWidget.selectedItemIndex = min(chestWidget.selectedItemIndex, len(tileEntityTag["Items"]) - 1) def deleteItem(): i = chestWidget.selectedItemIndex item = tileEntityTag["Items"][i] tileEntityTag["Items"].value = [t for t in tileEntityTag["Items"].value if t is not item] chestWidget.selectedItemIndex = min(chestWidget.selectedItemIndex, len(tileEntityTag["Items"]) - 1) def deleteEnable(): return len(tileEntityTag["Items"]) and chestWidget.selectedItemIndex != -1 def addEnable(): return len(tileEntityTag["Items"]) < chestWidget.itemLimit def addItem(): slot = 0 for item in tileEntityTag["Items"]: if slot == item["Slot"].value: slot += 1 if slot >= chestWidget.itemLimit: return item = pymclevel.TAG_Compound() item["id"] = pymclevel.TAG_String("minecraft:") item["Damage"] = pymclevel.TAG_Short(0) item["Slot"] = pymclevel.TAG_Byte(slot) item["Count"] = pymclevel.TAG_Byte(1) tileEntityTag["Items"].append(item) addItemButton = Button("New Item (1.7+)", action=addItem, enable=addEnable) deleteItemButton = Button("Delete This Item", action=deleteItem, enable=deleteEnable) deleteFromWorldButton = Button("Delete All Instances Of This Item From World", action=deleteFromWorld, enable=deleteEnable) deleteCol = Column((addItemButton, deleteItemButton, deleteFromWorldButton)) fieldRow = Row(fieldRow) col = Column((chestItemTable, fieldRow, deleteCol)) chestWidget.add(col) chestWidget.shrink_wrap() Dialog(client=chestWidget, responses=["Done"]).present() level = self.editor.level class ChestEditOperation(Operation): def __init__(self, tool, level): self.tool = tool self.level = level self.undoBackupEntityTag = undoBackupEntityTag self.canUndo = False def perform(self, recordUndo=True): if self.level.saving: alert("Cannot perform action while saving is taking place") return level.addTileEntity(tileEntityTag) self.canUndo = True def undo(self): self.redoBackupEntityTag = copy.deepcopy(tileEntityTag) level.addTileEntity(self.undoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntityTag), (1, 1, 1)) def redo(self): level.addTileEntity(self.redoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntityTag), (1, 1, 1)) if chestWidget.dirty: op = ChestEditOperation(self.editor, self.editor.level) self.editor.addOperation(op) if op.canUndo: self.editor.addUnsavedEdit() @mceutils.alertException def editFlowerPot(self, point): panel = Dialog() tileEntity = self.editor.level.tileEntityAt(*point) undoBackupEntityTag = copy.deepcopy(tileEntity) if not tileEntity: tileEntity = pymclevel.TAG_Compound() tileEntity["id"] = pymclevel.TAG_String(MCEDIT_DEFS.get("FlowerPot", "FlowerPot")) tileEntity["x"] = pymclevel.TAG_Int(point[0]) tileEntity["y"] = pymclevel.TAG_Int(point[1]) tileEntity["z"] = pymclevel.TAG_Int(point[2]) tileEntity["Item"] = pymclevel.TAG_String("") tileEntity["Data"] = pymclevel.TAG_Int(0) self.editor.level.addTileEntity(tileEntity) titleLabel = Label("Edit Flower Pot") Item = TextFieldWrapped(width=300, text=tileEntity["Item"].value) oldItem = Item.value Data = IntField(width=300,text=str(tileEntity["Data"].value)) oldData = Data.value class FlowerPotEditOperation(Operation): def __init__(self, tool, level): self.tool = tool self.level = level self.undoBackupEntityTag = undoBackupEntityTag self.canUndo = False def perform(self, recordUndo=True): if self.level.saving: alert("Cannot perform action while saving is taking place") return self.level.addTileEntity(tileEntity) self.canUndo = True def undo(self): self.redoBackupEntityTag = copy.deepcopy(tileEntity) self.level.addTileEntity(self.undoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntity), (1, 1, 1)) def redo(self): self.level.addTileEntity(self.redoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntity), (1, 1, 1)) def updateFlowerPot(): if oldData != Data.value or oldItem != Item.value: tileEntity["Item"] = pymclevel.TAG_String(Item.value) tileEntity["Data"] = pymclevel.TAG_Int(Data.value) op = FlowerPotEditOperation(self.editor, self.editor.level) self.editor.addOperation(op) if op.canUndo: self.editor.addUnsavedEdit() chunk = self.editor.level.getChunk(int(int(point[0]) / 16), int(int(point[2]) / 16)) chunk.dirty = True panel.dismiss() okBtn = Button("OK", action=updateFlowerPot) cancel = Button("Cancel", action=panel.dismiss) panel.add(Column((titleLabel, Row((Label("Item"), Item)), Row((Label("Data"), Data)), okBtn, cancel))) panel.shrink_wrap() panel.present() @mceutils.alertException def editEnchantmentTable(self, point): panel = Dialog() tileEntity = self.editor.level.tileEntityAt(*point) undoBackupEntityTag = copy.deepcopy(tileEntity) if not tileEntity: tileEntity = pymclevel.TAG_Compound() tileEntity["id"] = pymclevel.TAG_String(MCEDIT_DEFS.get("EnchantTable", "EnchantTable")) tileEntity["x"] = pymclevel.TAG_Int(point[0]) tileEntity["y"] = pymclevel.TAG_Int(point[1]) tileEntity["z"] = pymclevel.TAG_Int(point[2]) tileEntity["CustomName"] = pymclevel.TAG_String("") self.editor.level.addTileEntity(tileEntity) titleLabel = Label("Edit Enchantment Table") try: name = tileEntity["CustomName"].value except: name = "" name = TextFieldWrapped(width=300, text=name) oldName = name.value class EnchantmentTableEditOperation(Operation): def __init__(self, tool, level): self.tool = tool self.level = level self.undoBackupEntityTag = undoBackupEntityTag self.canUndo = False def perform(self, recordUndo=True): if self.level.saving: alert("Cannot perform action while saving is taking place") return self.level.addTileEntity(tileEntity) self.canUndo = True def undo(self): self.redoBackupEntityTag = copy.deepcopy(tileEntity) self.level.addTileEntity(self.undoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntity), (1, 1, 1)) def redo(self): self.level.addTileEntity(self.redoBackupEntityTag) return pymclevel.BoundingBox(pymclevel.TileEntity.pos(tileEntity), (1, 1, 1)) def updateEnchantmentTable(): if oldName != name.value: tileEntity["CustomName"] = pymclevel.TAG_String(name.value) op = EnchantmentTableEditOperation(self.editor, self.editor.level) self.editor.addOperation(op) if op.canUndo: self.editor.addUnsavedEdit() chunk = self.editor.level.getChunk(int(int(point[0]) / 16), int(int(point[2]) / 16)) chunk.dirty = True panel.dismiss() okBtn = Button("OK", action=updateEnchantmentTable) cancel = Button("Cancel", action=panel.dismiss) panel.add(Column((titleLabel, Row((Label("Custom Name"), name)), okBtn, cancel))) panel.shrink_wrap() panel.present() should_lock = False
[docs] def rightClickDown(self, evt): # self.rightMouseDragStart = datetime.now() self.should_lock = True self.toggleMouseLook()
[docs] def rightClickUp(self, evt): if not get_top_widget().is_modal: return if not self.should_lock and self.editor.level: self.should_lock = False self.toggleMouseLook() # if self.rightMouseDragStart is None: # return # td = datetime.now() - self.rightMouseDragStart # # except AttributeError: # # return # # print "RightClickUp: ", td # if td.microseconds > 180000: # self.mouseLookOff()
[docs] def leftClickDown(self, evt): self.editor.toolMouseDown(evt, self.blockFaceUnderCursor) if evt.num_clicks == 2: def distance2(p1, p2): return numpy.sum(map(lambda a, b: (a - b) ** 2, p1, p2)) point, face = self.blockFaceUnderCursor if point is not None: point = map(lambda x: int(numpy.floor(x)), point) if self.editor.currentTool is self.editor.selectionTool: try: block = self.editor.level.blockAt(*point) if distance2(point, self.cameraPosition) > 4: blockEditors = { pymclevel.alphaMaterials.MonsterSpawner.ID: self.editMonsterSpawner, pymclevel.alphaMaterials.Sign.ID: self.editSign, pymclevel.alphaMaterials.WallSign.ID: self.editSign, pymclevel.alphaMaterials.MobHead.ID: self.editSkull, pymclevel.alphaMaterials.CommandBlock.ID: self.editCommandBlock, 210: self.editCommandBlock, 211: self.editCommandBlock, pymclevel.alphaMaterials.Jukebox.ID: self.editJukebox, pymclevel.alphaMaterials.NoteBlock.ID: self.editNoteBlock, pymclevel.alphaMaterials.FlowerPot.ID: self.editFlowerPot, pymclevel.alphaMaterials.EnchantmentTable.ID: self.editEnchantmentTable } edit = blockEditors.get(block) if edit: self.editor.endSelection() edit(point) else: # detect "container" tiles te = self.editor.level.tileEntityAt(*point) if te and "Items" in te and "id" in te: self.editor.endSelection() self.editContainer(point, te["id"].value) except (EnvironmentError, pymclevel.ChunkNotPresent): pass
[docs] def leftClickUp(self, evt): self.editor.toolMouseUp(evt, self.blockFaceUnderCursor) # --- Event handlers ---
[docs] def mouse_down(self, evt): button = keys.remapMouseButton(evt.button) logging.debug("Mouse down %d @ %s", button, evt.pos) if button == 1: if sys.platform == "darwin" and evt.ctrl: self.rightClickDown(evt) else: self.leftClickDown(evt) elif button == 2: self.rightClickDown(evt) elif button == 3 and sys.platform == "darwin" and evt.alt: self.leftClickDown(evt) else: evt.dict['keyname'] = "mouse{}".format(button) self.editor.key_down(evt) self.editor.focus_on(None) # self.focus_switch = None
[docs] def mouse_up(self, evt): button = keys.remapMouseButton(evt.button) logging.debug("Mouse up %d @ %s", button, evt.pos) if button == 1: if sys.platform == "darwin" and evt.ctrl: self.rightClickUp(evt) else: self.leftClickUp(evt) elif button == 2: self.rightClickUp(evt) elif button == 3 and sys.platform == "darwin" and evt.alt: self.leftClickUp(evt) else: evt.dict['keyname'] = "mouse{}".format(button) self.editor.key_up(evt)
[docs] def mouse_drag(self, evt): self.mouse_move(evt) self.editor.mouse_drag(evt)
lastRendererUpdate = datetime.now()
[docs] def mouse_move(self, evt): if self.avoidMouseJumpBug == 2: self.avoidMouseJumpBug = 0 return def sensitivityAdjust(d): return d * config.controls.mouseSpeed.get() / 10.0 self.editor.mouseEntered = True if self.mouseMovesCamera: self.should_lock = False pitchAdjust = sensitivityAdjust(evt.rel[1]) if self.invertMousePitch: pitchAdjust = -pitchAdjust self.yaw += sensitivityAdjust(evt.rel[0]) self.pitch += pitchAdjust if datetime.now() - self.lastRendererUpdate > timedelta(0, 0, 500000): self.editor.renderer.loadNearbyChunks() self.lastRendererUpdate = datetime.now() # adjustLimit = 2 # self.oldMousePosition = (x, y) # if (self.startingMousePosition[0] - x > adjustLimit or self.startingMousePosition[1] - y > adjustLimit or # self.startingMousePosition[0] - x < -adjustLimit or self.startingMousePosition[1] - y < -adjustLimit): # mouse.set_pos(*self.startingMousePosition) # event.get(MOUSEMOTION) # self.oldMousePosition = (self.startingMousePosition) #if config.settings.showCommands.get():
[docs] def activeevent(self, evt): if evt.state & 0x2 and evt.gain != 0: self.avoidMouseJumpBug = 1
@property def tooltipText(self): #if self.hoveringCommandBlock[0] and (self.editor.currentTool is self.editor.selectionTool and self.editor.selectionTool.infoKey == 0): # return self.hoveringCommandBlock[1] or "[Empty]" if self.editor.currentTool is self.editor.selectionTool and self.editor.selectionTool.infoKey == 0 and config.settings.showQuickBlockInfo.get(): point, face = self.blockFaceUnderCursor if point: if not self.block_info_parsers or (BlockInfoParser.last_level != self.editor.level): self.block_info_parsers = BlockInfoParser.get_parsers(self.editor) block = self.editor.level.blockAt(*point) if block: if block in self.block_info_parsers: return self.block_info_parsers[block](point) return self.editor.currentTool.worldTooltipText floorQuad = numpy.array(((-4000.0, 0.0, -4000.0), (-4000.0, 0.0, 4000.0), (4000.0, 0.0, 4000.0), (4000.0, 0.0, -4000.0), ), dtype='float32')
[docs] def updateFloorQuad(self): floorQuad = ((-4000.0, 0.0, -4000.0), (-4000.0, 0.0, 4000.0), (4000.0, 0.0, 4000.0), (4000.0, 0.0, -4000.0), ) floorQuad = numpy.array(floorQuad, dtype='float32') if self.editor.renderer.inSpace(): floorQuad *= 8.0 floorQuad += (self.cameraPosition[0], 0.0, self.cameraPosition[2]) self.floorQuad = floorQuad self.floorQuadList.invalidate()
[docs] def drawFloorQuad(self): self.floorQuadList.call(self._drawFloorQuad)
@staticmethod def _drawCeiling(): lines = [] minz = minx = -256 maxz = maxx = 256 for x in range(minx, maxx + 1, 16): lines.append((x, 0, minz)) lines.append((x, 0, maxz)) for z in range(minz, maxz + 1, 16): lines.append((minx, 0, z)) lines.append((maxx, 0, z)) GL.glColor(0.3, 0.7, 0.9) GL.glVertexPointer(3, GL.GL_FLOAT, 0, numpy.array(lines, dtype='float32')) GL.glEnable(GL.GL_DEPTH_TEST) GL.glDepthMask(False) GL.glDrawArrays(GL.GL_LINES, 0, len(lines)) GL.glDisable(GL.GL_DEPTH_TEST) GL.glDepthMask(True)
[docs] def drawCeiling(self): GL.glMatrixMode(GL.GL_MODELVIEW) # GL.glPushMatrix() x, y, z = self.cameraPosition x -= x % 16 z -= z % 16 y = self.editor.level.Height GL.glTranslate(x, y, z) self.ceilingList.call(self._drawCeiling) GL.glTranslate(-x, -y, -z)
_floorQuadList = None @property def floorQuadList(self): if not self._floorQuadList: self._floorQuadList = glutils.DisplayList() return self._floorQuadList _ceilingList = None @property def ceilingList(self): if not self._ceilingList: self._ceilingList = glutils.DisplayList() return self._ceilingList @property def floorColor(self): if self.drawSky: return 0.0, 0.0, 1.0, 0.3 else: return 0.0, 1.0, 0.0, 0.15 # floorColor = (0.0, 0.0, 1.0, 0.1) def _drawFloorQuad(self): GL.glDepthMask(True) GL.glPolygonOffset(DepthOffset.ChunkMarkers + 2, DepthOffset.ChunkMarkers + 2) GL.glVertexPointer(3, GL.GL_FLOAT, 0, self.floorQuad) GL.glColor(*self.floorColor) with gl.glEnable(GL.GL_BLEND, GL.GL_DEPTH_TEST, GL.GL_POLYGON_OFFSET_FILL): GL.glDrawArrays(GL.GL_QUADS, 0, 4) @property def drawSky(self): return self._drawSky @drawSky.setter def drawSky(self, val): self._drawSky = val if self.skyList: self.skyList.invalidate() if self._floorQuadList: self._floorQuadList.invalidate() skyList = None
[docs] def drawSkyBackground(self): if self.skyList is None: self.skyList = glutils.DisplayList() self.skyList.call(self._drawSkyBackground)
def _drawSkyBackground(self): GL.glMatrixMode(GL.GL_MODELVIEW) GL.glPushMatrix() GL.glLoadIdentity() GL.glMatrixMode(GL.GL_PROJECTION) GL.glPushMatrix() GL.glLoadIdentity() GL.glEnableClientState(GL.GL_COLOR_ARRAY) quad = numpy.array([-1, -1, -1, 1, 1, 1, 1, -1], dtype='float32') if self.editor.level.dimNo == -1: colors = numpy.array([0x90, 0x00, 0x00, 0xff, 0x90, 0x00, 0x00, 0xff, 0x90, 0x00, 0x00, 0xff, 0x90, 0x00, 0x00, 0xff, ], dtype='uint8') elif self.editor.level.dimNo == 1: colors = numpy.array([0x22, 0x27, 0x28, 0xff, 0x22, 0x27, 0x28, 0xff, 0x22, 0x27, 0x28, 0xff, 0x22, 0x27, 0x28, 0xff, ], dtype='uint8') else: colors = numpy.array([0x48, 0x49, 0xBA, 0xff, 0x8a, 0xaf, 0xff, 0xff, 0x8a, 0xaf, 0xff, 0xff, 0x48, 0x49, 0xBA, 0xff, ], dtype='uint8') alpha = 1.0 if alpha > 0.0: if alpha < 1.0: GL.glEnable(GL.GL_BLEND) GL.glVertexPointer(2, GL.GL_FLOAT, 0, quad) GL.glColorPointer(4, GL.GL_UNSIGNED_BYTE, 0, colors) GL.glDrawArrays(GL.GL_QUADS, 0, 4) if alpha < 1.0: GL.glDisable(GL.GL_BLEND) GL.glDisableClientState(GL.GL_COLOR_ARRAY) GL.glMatrixMode(GL.GL_PROJECTION) GL.glPopMatrix() GL.glMatrixMode(GL.GL_MODELVIEW) GL.glPopMatrix() enableMouseLag = config.settings.enableMouseLag.property() @property def drawFog(self): return self._drawFog and not self.editor.renderer.inSpace() @drawFog.setter def drawFog(self, val): self._drawFog = val fogColor = numpy.array([0.6, 0.8, 1.0, 1.0], dtype='float32') fogColorBlack = numpy.array([0.0, 0.0, 0.0, 1.0], dtype='float32')
[docs] def enableFog(self): GL.glEnable(GL.GL_FOG) if self.drawSky: GL.glFogfv(GL.GL_FOG_COLOR, self.fogColor) else: GL.glFogfv(GL.GL_FOG_COLOR, self.fogColorBlack) GL.glFogf(GL.GL_FOG_DENSITY, 0.0001 * config.settings.fogIntensity.get())
@staticmethod
[docs] def disableFog(): GL.glDisable(GL.GL_FOG)
[docs] def getCameraPoint(self): distance = self.editor.currentTool.cameraDistance return [i for i in itertools.imap(lambda p, d: int(numpy.floor(p + d * distance)), self.cameraPosition, self.cameraVector)]
blockFaceUnderCursor = (0, 0, 0), (0, 0, 0) viewingFrustum = None
[docs] def setup_projection(self): distance = 1.0 if self.editor.renderer.inSpace(): distance = 8.0 GLU.gluPerspective(max(self.fov, 25.0), self.ratio, self.near * distance, self.far * distance)
[docs] def setup_modelview(self): self.setModelview()
[docs] def gl_draw(self): self.tickCamera(self.editor.frameStartTime, self.editor.cameraInputs, self.editor.renderer.inSpace()) self.render()
[docs] def render(self): self.viewingFrustum = frustum.Frustum.fromViewingMatrix() if self.superSecretSettings: self.editor.drawStars() if self.drawSky: self.drawSkyBackground() if self.drawFog: self.enableFog() self.drawFloorQuad() self.editor.renderer.viewingFrustum = self.viewingFrustum self.editor.renderer.draw() if self.showCeiling and not self.editor.renderer.inSpace(): self.drawCeiling() if self.editor.level: try: self.updateBlockFaceUnderCursor() except (EnvironmentError, pymclevel.ChunkNotPresent) as e: logging.debug("Updating cursor block: %s", e) self.blockFaceUnderCursor = (None, None) self.root.update_tooltip() (blockPosition, faceDirection) = self.blockFaceUnderCursor if None != blockPosition: self.editor.updateInspectionString(blockPosition) if self.find_widget(mouse.get_pos()) == self: ct = self.editor.currentTool if ct: ct.drawTerrainReticle() ct.drawToolReticle() else: self.editor.drawWireCubeReticle() for t in self.editor.toolbar.tools: t.drawTerrainMarkers() t.drawToolMarkers() if self.drawFog: self.disableFog() if self.compassToggle: if self._compass is None: self._compass = CompassOverlay() x = getattr(getattr(self.editor, 'copyPanel', None), 'width', 0) if x: x = x /float( self.editor.mainViewport.width) self._compass.x = x self._compass.yawPitch = self.yaw, 0 with gl.glPushMatrix(GL.GL_PROJECTION): GL.glLoadIdentity() GL.glOrtho(0., 1., float(self.height) / self.width, 0, -200, 200) self._compass.draw() else: self._compass = None
_compass = None
[docs]class BlockInfoParser(object): last_level = None nbt_ending = "\n\nPress ALT for NBT" edit_ending = ", Double-Click to Edit" @classmethod
[docs] def get_parsers(cls, editor): cls.last_level = editor.level parser_map = {} for subcls in cls.__subclasses__(): instance = subcls(editor.level) try: blocks = instance.getBlocks() except KeyError: continue if isinstance(blocks, (str, int)): parser_map[blocks] = instance.parse_info elif isinstance(blocks, (list, tuple)): for block in blocks: parser_map[block] = instance.parse_info return parser_map
[docs] def getBlocks(self): raise NotImplementedError()
[docs] def parse_info(self, pos): raise NotImplementedError()
[docs]class SpawnerInfoParser(BlockInfoParser): def __init__(self, level): self.level = level
[docs] def getBlocks(self): return self.level.materials["minecraft:mob_spawner"].ID
[docs] def parse_info(self, pos): tile_entity = self.level.tileEntityAt(*pos) if tile_entity: spawn_data = tile_entity.get("SpawnData", {}) if spawn_data: id = spawn_data.get('EntityId', None) if not id: id = spawn_data.get('id', None) if not id: value = repr(NameError("Malformed spawn data: could not find 'EntityId' or 'id' tag.")) else: value = id.value return str(value) + " Spawner" + self.nbt_ending + self.edit_ending return "[Empty]" + self.nbt_ending + self.edit_ending
[docs]class JukeboxInfoParser(BlockInfoParser): id_records = { 2256: "13", 2257: "Cat", 2258: "Blocks", 2259: "Chirp", 2260: "Far", 2261: "Mall", 2262: "Mellohi", 2263: "Stal", 2264: "Strad", 2265: "Ward", 2266: "11", 2267: "Wait" } name_records = { "minecraft:record_13": "13", "minecraft:record_cat": "Cat", "minecraft:record_blocks": "Blocks", "minecraft:record_chirp": "Chirp", "minecraft:record_far": "Far", "minecraft:record_mall": "Mall", "minecraft:record_mellohi": "Mellohi", "minecraft:record_stal": "Stal", "minecraft:record_strad": "Strad", "minecraft:record_ward": "Ward", "minecraft:record_11": "11", "minecraft:record_wait": "Wait" } def __init__(self, level): self.level = level
[docs] def getBlocks(self): return self.level.materials["minecraft:jukebox"].ID
[docs] def parse_info(self, pos): tile_entity = self.level.tileEntityAt(*pos) if tile_entity: if "Record" in tile_entity: value = tile_entity["Record"].value if value in self.id_records: return self.id_records[value] + " Record" + self.nbt_ending + self.edit_ending elif "RecordItem" in tile_entity: value = tile_entity["RecordItem"]["id"].value if value in self.name_records: return self.name_records[value] + " Record" + self.nbt_ending + self.edit_ending return "[No Record]" + self.nbt_ending + self.edit_ending
[docs]class CommandBlockInfoParser(BlockInfoParser): def __init__(self, level): self.level = level
[docs] def getBlocks(self): return [ self.level.materials["minecraft:command_block"].ID, self.level.materials["minecraft:repeating_command_block"].ID, self.level.materials["minecraft:chain_command_block"].ID ]
[docs] def parse_info(self, pos): tile_entity = self.level.tileEntityAt(*pos) if tile_entity: value = tile_entity.get("Command", TAG_String("")).value if value: if len(value) > 1500: return value[:1500] + "\n**COMMAND IS TOO LONG TO SHOW MORE**" + self.nbt_ending + self.edit_ending return value + self.nbt_ending + self.edit_ending return "[Empty Command Block]" + self.nbt_ending + self.edit_ending
[docs]class ContainerInfoParser(BlockInfoParser): def __init__(self, level): self.level = level
[docs] def getBlocks(self): return [ self.level.materials["minecraft:dispenser"].ID, self.level.materials["minecraft:chest"].ID, self.level.materials["minecraft:furnace"].ID, self.level.materials["minecraft:lit_furnace"].ID, self.level.materials["minecraft:trapped_chest"].ID, self.level.materials["minecraft:hopper"].ID, self.level.materials["minecraft:dropper"].ID, self.level.materials["minecraft:brewing_stand"].ID ]
[docs] def parse_info(self, pos): tile_entity = self.level.tileEntityAt(*pos) if tile_entity: return "Contains {} Items".format(len(tile_entity.get("Items", []))) + self.nbt_ending + self.edit_ending return "[Empty Container]" + self.nbt_ending + self.edit_ending
[docs]def unproject(x, y, z): try: return GLU.gluUnProject(x, y, z) except ValueError: # projection failed return 0, 0, 0