void ChunkRender(VoxelChunk chunk) { if (FastVector.SqrMinDistanceXZ(chunk.position, transform.position) < 32 * 32) { requireUpdateLighting = true; } }
void LateUpdate() { if (env == null || !env.applicationIsPlaying || !crosshairEnabled) { return; } UpdateCrosshairScreenPosition(); Ray ray = m_Camera.ScreenPointToRay(input.screenPos); VoxelHitInfo hitInfo; // Check if there's a voxel in range crosshairOnBlock = env.RayCast(ray, out hitInfo) && hitInfo.voxelIndex >= 0; if (!input.GetButton(InputButtonNames.Button1) || crosshairHitInfo.GetVoxelNow().isEmpty) { crosshairHitInfo = hitInfo; } if (crosshairOnBlock) { crosshairOnBlock = FastVector.SqrDistance(ref crosshairHitInfo.voxelCenter, ref curPos) < crosshairMaxDistance * crosshairMaxDistance; if (!crosshairOnBlock) { crosshairHitInfo.Clear(); } } if (changeOnBlock) { if (!crosshairOnBlock) { ResetCrosshairPosition(); return; } // Puts crosshair over the voxel but do it only if crosshair won't disappear because of the angle or it's switching from orbit to free mode (or viceversa) float d = Vector3.Dot(ray.direction, crosshairHitInfo.normal); if (d < -0.2f) { crosshair.position = hitInfo.point; crosshair.LookAt(hitInfo.point + crosshairHitInfo.normal); } else { crosshair.localRotation = Misc.quaternionZero; } crosshairMat.color = crosshairOnTargetColor; } if (crosshairOnBlock) { crosshair.localScale = Misc.vector3one * (crosshairScale * (1f - targetAnimationScale * 0.5f + Mathf.PingPong(Time.time * targetAnimationSpeed, targetAnimationScale))); env.VoxelHighlight(crosshairHitInfo, voxelHighlightColor, voxelHighlightEdge); } }
int DamageAreaFast(Vector3 origin, int damage, int damageRadius = 1, bool distanceAttenuation = true, bool addParticles = true, List <VoxelIndex> results = null) { bool hasResults = results != null; if (hasResults) { results.Clear(); } if (damageRadius < 0 || damage < 1) { return(0); } int damagedCount = 0; Vector3 direction = Misc.vector3zero; VoxelHitInfo hitInfo; GetVoxelIndices(origin, damageRadius, tempVoxelIndices); int count = tempVoxelIndices.Count; for (int k = 0; k < count; k++) { VoxelIndex vi = tempVoxelIndices [k]; VoxelChunk otherChunk = vi.chunk; int otherIndex = vi.voxelIndex; int dam = damage; if (distanceAttenuation && vi.sqrDistance > 1) { dam = (int)(damage * damageRadius * damageRadius / vi.sqrDistance); } if (dam > 0 && GetVoxelVisibility(otherChunk, otherIndex)) { FastVector.NormalizedDirection(ref origin, ref vi.position, ref direction); if (RayCastFast(origin, direction, out hitInfo, damageRadius, false, 5)) { int damageTaken = DamageVoxelFast(ref hitInfo, dam, addParticles, false); if (hasResults) { VoxelIndex di = vi; di.damageTaken = damageTaken; results.Add(di); damagedCount++; } } } } return(damagedCount); }
IEnumerator Consolidate() { if (gameObject == null) { yield break; } WaitForSeconds w = new WaitForSeconds(1f); VoxelChunk targetChunk; VoxelPlayEnvironment env = VoxelPlayEnvironment.instance; if (env.GetChunk(transform.position, out targetChunk, false)) { const float maxDist = 100 * 100; while (FastVector.SqrDistanceByValue(targetChunk.position, env.cameraMain.transform.position) < maxDist && env.ChunkIsInFrustum(targetChunk)) { yield return(w); } env.VoxelCancelDynamic(this); } }
bool HitVoxelFast(Vector3 origin, Vector3 direction, int damage, out VoxelHitInfo hitInfo, float maxDistance = 0, int damageRadius = 1, bool addParticles = true, bool playSound = true, bool allowDamageEvent = true) { RayCastFast(origin, direction, out hitInfo, maxDistance, false, 0, ColliderTypes.IgnorePlayer); VoxelChunk chunk = hitInfo.chunk; if (chunk == null || hitInfo.voxelIndex < 0) { lastHitInfo.chunk = null; lastHitInfo.voxelIndex = -1; return(false); } lastHitInfo = hitInfo; DamageVoxelFast(ref hitInfo, damage, addParticles, playSound, allowDamageEvent); bool button1Pressed = input.GetButton(InputButtonNames.Button1); bool button2Pressed = input.GetButton(InputButtonNames.Button2); if ((button1Pressed || button2Pressed) && OnVoxelClick != null) { OnVoxelClick(chunk, hitInfo.voxelIndex, button1Pressed ? 0 : 1, hitInfo); } if (damageRadius > 1) { Vector3 otherPos; Vector3 explosionPosition = hitInfo.voxelCenter + hitInfo.normal * damageRadius; damageRadius--; for (int y = -damageRadius; y <= damageRadius; y++) { otherPos.y = lastHitInfo.voxelCenter.y + y; for (int z = -damageRadius; z <= damageRadius; z++) { otherPos.z = lastHitInfo.voxelCenter.z + z; for (int x = -damageRadius; x <= damageRadius; x++) { if (x == 0 && z == 0 && y == 0) { continue; } VoxelChunk otherChunk; int otherIndex; otherPos.x = lastHitInfo.voxelCenter.x + x; if (GetVoxelIndex(otherPos, out otherChunk, out otherIndex, false)) { if (GetVoxelVisibility(otherChunk, otherIndex)) { FastVector.NormalizedDirection(ref explosionPosition, ref otherPos, ref direction); if (RayCast(explosionPosition, direction, out hitInfo)) { DamageVoxelFast(ref hitInfo, damage, addParticles, playSound, allowDamageEvent); } } } } } } } return(true); }
private void FixedUpdate() { if (!hasCharacterController) { return; } float speed; GetInput(out speed); Vector3 pos = transform.position; if (isFlying || isInWater) { m_MoveDir = m_Camera.transform.forward * m_Input.y + m_Camera.transform.right * m_Input.x + m_Camera.transform.up * m_Input.z; m_MoveDir *= speed; if (!isFlying) { if (m_MoveDir.y < 0) { m_MoveDir.y += 0.1f * Time.fixedDeltaTime; } if (m_Jump) { // Check if player is next to terrain if (env.CheckCollision(new Vector3(pos.x + m_Camera.transform.forward.x, pos.y, pos.z + m_Camera.transform.forward.z))) { m_MoveDir.y = jumpSpeed * 0.5f; m_Jumping = true; } m_Jump = false; } else { m_MoveDir += Physics.gravity * gravityMultiplier * Time.fixedDeltaTime * 0.5f; } if (pos.y > waterLevelTop && m_MoveDir.y > 0) { m_MoveDir.y = 0; // do not exit water } ProgressSwimCycle(m_CharacterController.velocity, swimSpeed); } } else { // always move along the camera forward as it is the direction that it being aimed at Vector3 desiredMove = transform.forward * m_Input.y + transform.right * m_Input.x; // get a normal for the surface that is being touched to move along it RaycastHit hitInfo; Physics.SphereCast(pos, m_CharacterController.radius, Misc.vector3down, out hitInfo, characterHeight / 2f, Physics.AllLayers, QueryTriggerInteraction.Ignore); desiredMove = Vector3.ProjectOnPlane(desiredMove, hitInfo.normal).normalized; m_MoveDir.x = desiredMove.x * speed; m_MoveDir.z = desiredMove.z * speed; if (m_CharacterController.isGrounded) { m_MoveDir.y = -stickToGroundForce; if (m_Jump) { m_MoveDir.y = jumpSpeed; PlayJumpSound(); m_Jump = false; m_Jumping = true; } } else { m_MoveDir += Physics.gravity * gravityMultiplier * Time.fixedDeltaTime; } UpdateCameraPosition(speed); ProgressStepCycle(m_CharacterController.velocity, speed); } Vector3 finalMove = m_MoveDir * Time.fixedDeltaTime; Vector3 newPos = pos + finalMove; bool canMove = !limitBoundsEnabled || limitBounds.Contains(newPos); if (m_PreviouslyGrounded && !isFlying && isCrouched) { // check if player is beyond the edge Ray ray = new Ray(newPos, Misc.vector3down); canMove = Physics.SphereCast(ray, 0.3f, 1f); // if player can't move, clamp movement along the edge and check again if (!canMove) { if (Mathf.Abs(m_MoveDir.z) > Mathf.Abs(m_MoveDir.x)) { m_MoveDir.x = 0; } else { m_MoveDir.z = 0; } finalMove = m_MoveDir * Time.fixedDeltaTime; newPos = pos + finalMove; ray.origin = newPos; canMove = Physics.SphereCast(ray, 0.3f, 1f); } } // if constructor is enabled, disable any movement if control key is pressed (reserved for special constructor actions) if (env.constructorMode && input.GetButton(InputButtonNames.LeftControl)) { canMove = false; } if (canMove) { // autoclimb Vector3 dir = new Vector3(m_MoveDir.x, 0, m_MoveDir.z); Vector3 basePos = new Vector3(pos.x, pos.y - characterHeight * 0.25f, pos.z); Ray ray = new Ray(basePos, dir); if (Physics.SphereCast(ray, 0.3f, 1f)) { m_CharacterController.stepOffset = 1.1f; } else { m_CharacterController.stepOffset = 0.2f; } m_CollisionFlags = m_CharacterController.Move(finalMove); } isGrounded = m_CharacterController.isGrounded; // Check limits if (orbitMode) { if (FastVector.ClampDistance(ref lookAt, ref pos, minDistance, maxDistance)) { m_CharacterController.transform.position = pos; } } mouseLook.UpdateCursorLock(); if (!isGrounded && !isFlying) { // Check current chunk VoxelChunk chunk = env.GetCurrentChunk(); if (chunk != null && !chunk.isRendered) { WaitForCurrentChunk(); return; } } }
void CheckChunksVisibleDistanceLoop(long maxFrameTime) { try { bool eventOut = OnChunkExitVisibleDistance != null; bool eventIn = OnChunkEnterVisibleDistance != null; int max = checkChunksVisibleDistanceIndex + 200; if (max >= chunksPoolLoadIndex) { max = chunksPoolLoadIndex; } float visibleDistanceSqr = (_visibleChunksDistance + 1) * CHUNK_SIZE; visibleDistanceSqr *= visibleDistanceSqr; while (checkChunksVisibleDistanceIndex < max) { VoxelChunk chunk = chunksPool[checkChunksVisibleDistanceIndex]; if (chunk.isRendered && !chunk.isCloud) { float dist = FastVector.SqrMaxDistanceXorZ(ref chunk.position, ref currentAnchorPos); if (dist > visibleDistanceSqr) { if (chunk.visibleDistanceStatus != ChunkVisibleDistanceStatus.OutOfVisibleDistance) { chunk.visibleDistanceStatus = ChunkVisibleDistanceStatus.OutOfVisibleDistance; if (unloadFarChunks || eventOut) { if (unloadFarChunks) { chunk.gameObject.SetActive(false); } if (eventOut) { OnChunkExitVisibleDistance(chunk); } if (stopWatch.ElapsedMilliseconds >= maxFrameTime) { break; } } } } else if (chunk.visibleDistanceStatus != ChunkVisibleDistanceStatus.WithinVisibleDistance) { chunk.visibleDistanceStatus = ChunkVisibleDistanceStatus.WithinVisibleDistance; if (unloadFarChunks || eventOut) { if (unloadFarChunks) { chunk.gameObject.SetActive(true); } if (eventIn) { OnChunkEnterVisibleDistance(chunk); } if (stopWatch.ElapsedMilliseconds >= maxFrameTime) { break; } } } } checkChunksVisibleDistanceIndex++; } if (checkChunksVisibleDistanceIndex >= chunksPoolLoadIndex) { checkChunksVisibleDistanceIndex = -1; } } catch (Exception ex) { ShowExceptionMessage(ex); } }
void LateUpdate() { Collider collider = null; if (env != null && env.characterController != null) { // Check object on the crosshair collider = env.characterController.crosshairHitInfo.collider; if (env.input.GetButtonDown(InputButtonNames.Action)) { if (collider != null) { VoxelPlayInteractiveObject obj = collider.GetComponentInChildren <VoxelPlayInteractiveObject> (); if (obj != null) { if (obj.triggerNearbyObjects) { for (int k = 0; k < nearCount; k++) { obj = nearObjs [k]; if (obj != null && obj.playerIsNear) { nearObjs [k].OnPlayerAction(); } } } else if (obj.playerIsNear) { obj.OnPlayerAction(); } } } } } // Check nearby objects // Get player position Vector3 playerPos = env.currentAnchorPos; // Check if player has moved since last frame int playerPosX = (int)playerPos.x; int playerPosY = (int)playerPos.y; int playerPosZ = (int)playerPos.z; if (playerPosX == lastPlayerPosX && playerPosY == lastPlayerPosY && playerPosZ == lastPlayerPosZ && collider == lastCollider) { return; } lastCollider = collider; lastPlayerPosX = playerPosX; lastPlayerPosY = playerPosY; lastPlayerPosZ = playerPosZ; // Check if player enters/exits the interaction area per object for (int k = 0; k < count; k++) { VoxelPlayInteractiveObject o = objs [k]; if (o != null && o.enabled) { Vector3 objPos = o.transform.position; float interactionDistanceSqr = o.interactionDistance * o.interactionDistance; float dist = FastVector.SqrDistance(ref playerPos, ref objPos); bool isNear = dist <= interactionDistanceSqr; if (o.playerIsNear && !isNear) { o.playerIsNear = false; // Remove from near list nearObjs [o.nearIndex] = null; if (o.nearIndex == nearCount - 1) { nearCount--; } o.nearIndex = 0; // Call event o.OnPlayerGoesAway(); } else if (isNear && !o.playerIsNear) { o.playerIsNear = true; o.nearIndex = AddToDynamicList(o, ref nearObjs, ref nearCount); // Call event o.OnPlayerApproach(); } } } }
void ModelPlace(Vector3 position, ModelDefinition model, ref Bounds bounds, int rotationDegrees = 0, float colorBrightness = 1f, bool fitTerrain = false, List <VoxelIndex> indices = null, int indexStart = -1, int indexEnd = -1) { if (model == null) { return; } if (indexStart < 0) { indexStart = 0; } if (indexEnd < 0) { indexEnd = model.bits.Length - 1; } Vector3 pos; int modelOneYRow = model.sizeZ * model.sizeX; int modelOneZRow = model.sizeX; int halfSizeX = model.sizeX / 2; int halfSizeZ = model.sizeZ / 2; if (rotationDegrees == 360) { switch (UnityEngine.Random.Range(0, 4)) { case 0: rotationDegrees = 90; break; case 1: rotationDegrees = 180; break; case 2: rotationDegrees = 270; break; } } bool indicesProvided = indices != null; if (indicesProvided && indexStart < 0 && indexEnd < 0) { indices.Clear(); } VoxelIndex index = new VoxelIndex(); VoxelChunk lastChunk = null; int tmp; Vector3 min = bounds.min; Vector3 max = bounds.max; for (int b = indexStart; b <= indexEnd; b++) { int bitIndex = model.bits [b].voxelIndex; int py = bitIndex / modelOneYRow; int remy = bitIndex - py * modelOneYRow; int pz = remy / modelOneZRow; int px = remy - pz * modelOneZRow; switch (rotationDegrees) { case 90: tmp = px; px = halfSizeZ - pz; pz = halfSizeX - tmp; break; case 180: px = halfSizeX - px; pz = halfSizeZ - pz; break; case 270: tmp = px; px = pz - halfSizeZ; pz = tmp - halfSizeX; break; default: px -= halfSizeX; pz -= halfSizeZ; break; } pos.x = position.x + model.offsetX + px; pos.y = position.y + model.offsetY + py; pos.z = position.z + model.offsetZ + pz; VoxelChunk chunk; int voxelIndex; if (GetVoxelIndex(pos, out chunk, out voxelIndex)) { Color32 color = model.bits [b].finalColor; VoxelDefinition vd = model.bits [b].voxelDefinition ?? defaultVoxel; bool emptyVoxel = model.bits [b].isEmpty; if (emptyVoxel) { chunk.voxels [voxelIndex] = Voxel.Empty; } else { if (colorBrightness != 1f) { color.r = (byte)(color.r * colorBrightness); color.g = (byte)(color.g * colorBrightness); color.b = (byte)(color.b * colorBrightness); } chunk.voxels [voxelIndex].Set(vd, color); // Add index if (indicesProvided) { index.chunk = chunk; index.voxelIndex = voxelIndex; index.position = pos; indices.Add(index); } if (pos.x < min.x) { min.x = pos.x; } if (pos.y < min.y) { min.y = pos.y; } if (pos.z < min.z) { min.z = pos.z; } if (pos.x > max.x) { max.x = pos.x; } if (pos.y > max.y) { max.y = pos.y; } if (pos.z > max.z) { max.z = pos.z; } } // Prevent tree population chunk.allowTrees = false; chunk.modified = true; if (fitTerrain && !emptyVoxel) { // Fill beneath row 1 if (py == 0) { Vector3 under = pos; under.y -= 1; for (int k = 0; k < 100; k++, under.y--) { VoxelChunk lowChunk; int vindex; GetVoxelIndex(under, out lowChunk, out vindex, false); if (lowChunk != null && lowChunk.voxels [vindex].opaque < FULL_OPAQUE) { lowChunk.voxels [vindex].Set(vd, color); if (lowChunk != lastChunk) { lastChunk = lowChunk; if (!lastChunk.inqueue) { ChunkRequestRefresh(lastChunk, true, true); } } } else { break; } } } } if (chunk != lastChunk) { lastChunk = chunk; if (!lastChunk.inqueue) { lastChunk.MarkAsInconclusive(); ChunkRequestRefresh(lastChunk, true, true); } } } } FastVector.Floor(ref min); FastVector.Ceiling(ref max); bounds.center = (max + min) * 0.5f; bounds.size = max - min; }
void ModelPlace(Vector3 position, ModelDefinition model, ref Bounds bounds, int rotationDegrees = 0, float colorBrightness = 1f, bool fitTerrain = false, List <VoxelIndex> indices = null, int indexStart = -1, int indexEnd = -1, bool useUnpopulatedChunks = false, bool refreshChunks = true) { if (model == null) { return; } if (indexStart < 0) { indexStart = 0; } if (indexEnd < 0) { indexEnd = model.bits.Length - 1; } Vector3 pos; int modelOneYRow = model.sizeZ * model.sizeX; int modelOneZRow = model.sizeX; if (rotationDegrees == 360) { switch (WorldRand.Range(0, 4)) { case 1: rotationDegrees = 90; break; case 2: rotationDegrees = 180; break; case 3: rotationDegrees = 270; break; } } int halfSizeX = model.sizeX / 2; int halfSizeZ = model.sizeZ / 2; Vector3 zeroPos = Quaternion.Euler(0, rotationDegrees, 0) * new Vector3(-halfSizeX, 0, -halfSizeZ); // ensure all voxel definitions are present //bool reloadTextures = false; for (int b = indexStart; b <= indexEnd; b++) { VoxelDefinition vd = model.bits [b].voxelDefinition; if (vd != null && vd.index == 0) { AddVoxelDefinition(vd); //reloadTextures = true; } } //if (reloadTextures) { // TODO: RML (AddVoxelDefinition already marks reload world textures flag) // LoadWorldTextures(); //} bool indicesProvided = indices != null; if (indicesProvided && indexStart < 0 && indexEnd < 0) { indices.Clear(); } VoxelIndex index = new VoxelIndex(); Vector3 min = bounds.min; Vector3 max = bounds.max; for (int b = indexStart; b <= indexEnd; b++) { int bitIndex = model.bits [b].voxelIndex; int py = bitIndex / modelOneYRow; int remy = bitIndex - py * modelOneYRow; int pz = remy / modelOneZRow; int px = remy - pz * modelOneZRow; float wx = zeroPos.x, wz = zeroPos.z; switch (rotationDegrees) { case 90: wx += pz; wz -= px; break; case 180: wx -= px; wz -= pz; break; case 270: wx -= pz; wz += px; break; default: wx += px; wz += pz; break; } pos.x = position.x + model.offsetX + wx; pos.y = position.y + model.offsetY + py; pos.z = position.z + model.offsetZ + wz; VoxelChunk chunk; int voxelIndex; if (useUnpopulatedChunks) { chunk = GetChunkUnpopulated(pos); } if (GetVoxelIndex(pos, out chunk, out voxelIndex)) { bool emptyVoxel = model.bits [b].isEmpty; if (emptyVoxel) { chunk.voxels [voxelIndex] = Voxel.Hole; } else { Color32 color = model.bits [b].finalColor; if (colorBrightness != 1f) { color.r = (byte)(color.r * colorBrightness); color.g = (byte)(color.g * colorBrightness); color.b = (byte)(color.b * colorBrightness); } VoxelDefinition vd = model.bits [b].voxelDefinition ?? defaultVoxel; chunk.voxels [voxelIndex].Set(vd, color); float rotation = (model.bits [b].rotation + 360 + rotationDegrees) % 360; if (rotation != 0) { chunk.voxels [voxelIndex].SetTextureRotation(Voxel.GetTextureRotationFromDegrees(rotation)); } // Add index if (indicesProvided) { index.chunk = chunk; index.voxelIndex = voxelIndex; index.position = pos; indices.Add(index); } if (pos.x < min.x) { min.x = pos.x; } if (pos.y < min.y) { min.y = pos.y; } if (pos.z < min.z) { min.z = pos.z; } if (pos.x > max.x) { max.x = pos.x; } if (pos.y > max.y) { max.y = pos.y; } if (pos.z > max.z) { max.z = pos.z; } if (fitTerrain) { // Fill beneath row 1 if (py == 0) { Vector3 under = pos; under.y -= 1; for (int k = 0; k < 100; k++, under.y--) { VoxelChunk lowChunk; int vindex; GetVoxelIndex(under, out lowChunk, out vindex, false); if (lowChunk != null && lowChunk.voxels [vindex].opaque < FULL_OPAQUE) { lowChunk.voxels [vindex].Set(vd, color); lowChunk.modified = true; if (!lowChunk.inqueue && !useUnpopulatedChunks) { ChunkRequestRefresh(lowChunk, true, true); } } else { break; } } } } } // Prevent tree population chunk.allowTrees = false; chunk.modified = true; if (!chunk.inqueue && !useUnpopulatedChunks) { chunk.MarkAsInconclusive(); if (refreshChunks) { ChunkRequestRefresh(chunk, true, true); } } } } FastVector.Floor(ref min); FastVector.Ceiling(ref max); bounds.center = (max + min) * 0.5f; bounds.size = max - min; }
void ModelPlaceTorches(Vector3 position, ModelDefinition model, int rotationDegrees = 0) { if (model == null || model.torches == null) { return; } FastVector.Floor(ref position); Vector3 pos; int modelOneYRow = model.sizeZ * model.sizeX; int modelOneZRow = model.sizeX; int halfSizeX = model.sizeX / 2; int halfSizeZ = model.sizeZ / 2; if (rotationDegrees == 360) { switch (UnityEngine.Random.Range(0, 4)) { case 0: rotationDegrees = 90; break; case 1: rotationDegrees = 180; break; case 2: rotationDegrees = 270; break; } } // ensure all voxel definitions are present int tmp; for (int b = 0; b < model.torches.Length; b++) { int bitIndex = model.torches [b].voxelIndex; int py = bitIndex / modelOneYRow; int remy = bitIndex - py * modelOneYRow; int pz = remy / modelOneZRow; int px = remy - pz * modelOneZRow; Vector3 normal = model.torches [b].normal; switch (rotationDegrees) { case 90: tmp = px; px = halfSizeZ - pz; pz = halfSizeX - tmp; tmp = (int)normal.x; normal.x = normal.z; normal.z = -tmp; break; case 180: px = halfSizeX - px; pz = halfSizeZ - pz; normal.x = -normal.x; normal.z = -normal.z; break; case 270: tmp = px; px = pz - halfSizeZ; pz = tmp - halfSizeX; tmp = (int)normal.x; normal.x = normal.z; normal.z = tmp; break; default: px -= halfSizeX; pz -= halfSizeZ; break; } pos.x = position.x + model.offsetX + px; pos.y = position.y + model.offsetY + py; pos.z = position.z + model.offsetZ + pz; pos -= normal; VoxelChunk chunk; int voxelIndex; VoxelHitInfo hitInfo = new VoxelHitInfo(); if (GetVoxelIndex(pos, out chunk, out voxelIndex)) { hitInfo.chunk = chunk; hitInfo.voxelIndex = voxelIndex; hitInfo.normal = normal; hitInfo.voxelCenter = pos + Misc.vector3half; TorchAttach(hitInfo, model.torches [b].itemDefinition, false); } } }