# ============================================================================
# FreeFormCamera.py (PyWC3 / Python -> Lua for Warcraft III)
# Requires: https://github.com/Blimba/PyWC3
# ============================================================================
from std.index import * # AddScriptHook, hook points, std helpers
# These are for IDE completion; PyWC3 treats them as defs-only. Optional but handy:
from df.commonj import * # JASS natives/constants (for code completion)
from df.blizzardj import * # Blizzard UI/Frame natives (for code completion)
# ============================================================================
# CONFIG
# ============================================================================
TOGGLE_KEY = OSKEY_CONTROL
ROTATION_SENSITIVITY = 90.0
MOVEMENT_SPEED = 2500.0
VERTICAL_SPEED = 1800.0
ZOOM_SENSITIVITY = 50.0
ZOOM_ACCEL = 6.0
DRAG_PIXEL_SCALE = 1.0
MIN_ZOOM_DISTANCE = 100.0
MAX_ZOOM_DISTANCE = 4000.0
LERP_FACTOR = 0.25
# ============================================================================
# STATE
# ============================================================================
_timer = None
_mouseWheelFrame = None
_isCameraActive = False
_isLeftMouseDown = False
_isRightMouseDown = False
_isShiftDown = False
_lastMouseX, _lastMouseY = 0.0, 0.0
_isDraggingLeft, _isDraggingRight = False, False
_targetX, _targetY, _targetZ = 0.0, 0.0, 0.0
_targetYaw, _targetPitch = 0.0, 0.0
_targetDist = 1650.0
_currX, _currY, _currZ = 0.0, 0.0, 0.0
_currYaw, _currPitch = 0.0, 0.0
_currDist = 1650.0
# ============================================================================
# CAMERA LOGIC
# ============================================================================
def _normalize360(r: float) -> float:
# Use the JASS native to match in-game semantics exactly.
r = ModuloReal(r, 360.0)
if r < 0.0:
r += 360.0
return r
def _update_camera():
global _currX, _currY, _currZ, _currYaw, _currPitch, _currDist
if not _isCameraActive:
return
# Lerp towards target
_currX += (_targetX - _currX) * LERP_FACTOR
_currY += (_targetY - _currY) * LERP_FACTOR
_currZ += (_targetZ - _currZ) * LERP_FACTOR
_currYaw += (_targetYaw - _currYaw) * LERP_FACTOR
_currPitch += (_targetPitch - _currPitch) * LERP_FACTOR
_currDist += (_targetDist - _currDist) * LERP_FACTOR
# Apply
SetCameraPosition(_currX, _currY)
SetCameraField(CAMERA_FIELD_ZOFFSET, _currZ, 0.00)
SetCameraField(CAMERA_FIELD_ROTATION, _normalize360(_currYaw), 0.00)
SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, _normalize360(_currPitch), 0.00)
SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, _currDist, 0.00)
def _on_mouse_move():
global _isDraggingLeft, _isDraggingRight, _lastMouseX, _lastMouseY
global _targetX, _targetY, _targetZ, _targetYaw, _targetPitch
if not _isCameraActive:
return
curX = BlzGetTriggerPlayerMouseX()
curY = BlzGetTriggerPlayerMouseY()
if _isLeftMouseDown and not _isDraggingLeft:
_isDraggingLeft = True
_lastMouseX, _lastMouseY = curX, curY
return
if _isRightMouseDown and not _isDraggingRight:
_isDraggingRight = True
_lastMouseX, _lastMouseY = curX, curY
return
dx = curX - _lastMouseX
dy = curY - _lastMouseY
if _isDraggingLeft or _isDraggingRight:
if _isLeftMouseDown:
yawRad = _targetYaw * bj_DEGTORAD
fwdX = Cos(yawRad)
fwdY = Sin(yawRad)
strafeX = Sin(yawRad)
strafeY = -Cos(yawRad)
_targetX += (strafeX * dx - fwdX * dy) * MOVEMENT_SPEED * DRAG_PIXEL_SCALE
_targetY += (strafeY * dx - fwdY * dy) * MOVEMENT_SPEED * DRAG_PIXEL_SCALE
if _isRightMouseDown:
if _isShiftDown:
_targetZ -= dy * VERTICAL_SPEED
else:
_targetYaw = _normalize360(_targetYaw - dx * ROTATION_SENSITIVITY)
_targetPitch = _normalize360(_targetPitch - dy * ROTATION_SENSITIVITY)
_lastMouseX, _lastMouseY = curX, curY
def _on_mouse_down():
global _isLeftMouseDown, _isRightMouseDown
if not _isCameraActive:
return
btn = BlzGetTriggerPlayerMouseButton()
if btn == MOUSE_BUTTON_TYPE_LEFT:
_isLeftMouseDown = True
elif btn == MOUSE_BUTTON_TYPE_RIGHT:
_isRightMouseDown = True
def _on_mouse_up():
global _isLeftMouseDown, _isRightMouseDown, _isDraggingLeft, _isDraggingRight
btn = BlzGetTriggerPlayerMouseButton()
if btn == MOUSE_BUTTON_TYPE_LEFT:
_isLeftMouseDown = False
_isDraggingLeft = False
elif btn == MOUSE_BUTTON_TYPE_RIGHT:
_isRightMouseDown = False
_isDraggingRight = False
def _on_mouse_wheel():
global _targetDist
if not _isCameraActive:
return
raw = BlzGetTriggerFrameValue()
if raw == 0.0:
return
steps = ZOOM_SENSITIVITY * ZOOM_ACCEL
if raw > 0.0:
_targetDist -= steps
else:
_targetDist += steps
if _targetDist < MIN_ZOOM_DISTANCE:
_targetDist = MIN_ZOOM_DISTANCE
elif _targetDist > MAX_ZOOM_DISTANCE:
_targetDist = MAX_ZOOM_DISTANCE
def _sync_camera_state():
global _currX, _currY, _currZ, _currYaw, _currPitch, _currDist
global _targetX, _targetY, _targetZ, _targetYaw, _targetPitch, _targetDist
camX = GetCameraTargetPositionX()
camY = GetCameraTargetPositionY()
camZ = GetCameraField(CAMERA_FIELD_ZOFFSET)
camYaw = GetCameraField(CAMERA_FIELD_ROTATION) # degrees
camPitch = GetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK) # degrees
camDist = GetCameraField(CAMERA_FIELD_TARGET_DISTANCE)
_currX = _targetX = camX
_currY = _targetY = camY
_currZ = _targetZ = camZ
_currYaw = _targetYaw = camYaw
_currPitch = _targetPitch = camPitch
_currDist = _targetDist = camDist
def _on_toggle_camera():
global _isCameraActive, _timer, _mouseWheelFrame
global _isLeftMouseDown, _isRightMouseDown, _isDraggingLeft, _isDraggingRight
_isCameraActive = not _isCameraActive
p = GetTriggerPlayer()
if _isCameraActive:
_sync_camera_state()
TimerStart(_timer, 0.03, True, _update_camera)
EnableSelect(False)
EnablePreSelect(False)
BlzFrameSetVisible(_mouseWheelFrame, True)
DisplayTimedTextToPlayer(p, 0.0, 0.0, 2.0, "Free Camera Enabled")
else:
PauseTimer(_timer)
EnableSelect(True)
EnablePreSelect(True)
BlzFrameSetVisible(_mouseWheelFrame, False)
DisplayTimedTextToPlayer(p, 0.0, 0.0, 2.0, "Free Camera Disabled")
# Reset drag state
_isLeftMouseDown = False
_isRightMouseDown = False
_isDraggingLeft = False
_isDraggingRight = False
def _on_key_event():
global _isShiftDown
if BlzGetTriggerPlayerKey() == OSKEY_SHIFT:
_isShiftDown = BlzGetTriggerPlayerIsKeyDown()
# ============================================================================
# INIT
# ============================================================================
def _init():
global _timer, _mouseWheelFrame
CameraSetSmoothingFactor(100)
_timer = CreateTimer()
_mouseWheelFrame = BlzCreateFrameByType(
"TEXT", "MouseWheelFrame", BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), "", 0
)
BlzFrameSetAllPoints(_mouseWheelFrame, BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0))
BlzFrameSetVisible(_mouseWheelFrame, False)
trig = CreateTrigger()
BlzTriggerRegisterFrameEvent(trig, _mouseWheelFrame, FRAMEEVENT_MOUSE_WHEEL)
TriggerAddAction(trig, _on_mouse_wheel)
for i in range(0, bj_MAX_PLAYER_SLOTS):
pl = Player(i)
if GetPlayerSlotState(pl) == PLAYER_SLOT_STATE_PLAYING:
trig = CreateTrigger()
TriggerRegisterPlayerEvent(trig, pl, EVENT_PLAYER_MOUSE_MOVE)
TriggerAddAction(trig, _on_mouse_move)
trig = CreateTrigger()
TriggerRegisterPlayerEvent(trig, pl, EVENT_PLAYER_MOUSE_DOWN)
TriggerAddAction(trig, _on_mouse_down)
trig = CreateTrigger()
TriggerRegisterPlayerEvent(trig, pl, EVENT_PLAYER_MOUSE_UP)
TriggerAddAction(trig, _on_mouse_up)
trig = CreateTrigger()
BlzTriggerRegisterPlayerKeyEvent(trig, pl, TOGGLE_KEY, 0, True)
TriggerAddAction(trig, _on_toggle_camera)
trig = CreateTrigger()
BlzTriggerRegisterPlayerKeyEvent(trig, pl, OSKEY_SHIFT, 0, True)
TriggerAddAction(trig, _on_key_event)
trig = CreateTrigger()
BlzTriggerRegisterPlayerKeyEvent(trig, pl, OSKEY_SHIFT, 0, False)
TriggerAddAction(trig, _on_key_event)
def setup():
"""Call once (e.g., from your map’s main python file)."""
AddScriptHook(_init, MAIN_AFTER)