polymorpheffect.cs
Compiled
/// <summary>
/// Applied to an NPC when hit by polymorph rounds.
/// Hides the NPC visuals and AI, spawns a random prop in its place,
/// then restores everything after the duration expires.
///
/// The NPC capsule collider stays active the whole time so the player
/// can still shoot the target while it is disguised.
/// </summary>
public class PolymorphEffect : MonoBehaviour
{
// ------------------------------------------------------------------ //
// Runtime state
// ------------------------------------------------------------------ //
private float duration;
private float timeRemaining;
private GameObject[] propPool;
private GameObject spawnedProp;
private bool showDebug;
// Cached components
private NavMeshAgent navAgent;
private Animator animator;
private float originalAnimatorSpeed;
private bool hadNavAgent;
private bool hadAnimator;
// All renderers on the NPC (excludes the spawned prop)
private SkinnedMeshRenderer[] npcRenderers;
// ------------------------------------------------------------------ //
// Public API
// ------------------------------------------------------------------ //
public void Initialize(float polymorphDuration, GameObject[] pool, bool debug)
{
duration = polymorphDuration;
timeRemaining = duration;
propPool = pool;
showDebug = debug;
ApplyPolymorph();
Debug.Log("[PolymorphEffect] Started on " + gameObject.name + " for " + duration + "s");
}
public void RefreshEffect(float polymorphDuration, GameObject[] pool, bool debug)
{
duration = polymorphDuration;
timeRemaining = duration;
propPool = pool;
showDebug = debug;
// Reroll the prop on refresh for variety
if (spawnedProp != null)
{
Destroy(spawnedProp);
}
SpawnProp();
Debug.Log("[PolymorphEffect] Refreshed on " + gameObject.name);
}
// ------------------------------------------------------------------ //
// Core logic
// ------------------------------------------------------------------ //
private void ApplyPolymorph()
{
EnemyHealth enemyHealth = GetComponent<EnemyHealth>();
if (enemyHealth != null)
{
enemyHealth.isPolymorphed = true;
}
// --- Disable NavMeshAgent ---
navAgent = GetComponent<NavMeshAgent>();
if (navAgent != null)
{
hadNavAgent = true;
navAgent.isStopped = true;
navAgent.velocity = Vector3.zero;
Debug.Log("[PolymorphEffect] Stopped NavMeshAgent on " + gameObject.name);
}
// --- Freeze Animator ---
animator = GetComponent<Animator>();
if (animator != null)
{
hadAnimator = true;
originalAnimatorSpeed = animator.speed;
animator.speed = 0f;
Debug.Log("[PolymorphEffect] Froze Animator on " + gameObject.name);
}
// --- Hide NPC renderers ---
// Collect now, before the prop is spawned, so we don't accidentally
// grab renderers from the prop if it gets parented here later.
npcRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();
foreach (Renderer r in npcRenderers)
{
if (r != null)
{
r.enabled = false;
}
}
Debug.Log("[PolymorphEffect] Hid " + npcRenderers.Length + " renderers on " + gameObject.name);
// --- Spawn replacement prop ---
SpawnProp();
}
private void SpawnProp()
{
if (propPool == null || propPool.Length == 0)
{
Debug.LogWarning("[PolymorphEffect] Prop pool is empty! Assign prefabs in ModifierData.");
return;
}
// Pick a random entry from the pool (ignore null slots)
List<GameObject> validProps = new List<GameObject>();
foreach (GameObject p in propPool)
{
if (p != null)
{
validProps.Add(p);
}
}
if (validProps.Count == 0)
{
Debug.LogWarning("[PolymorphEffect] All prop pool entries are null!");
return;
}
int index = Random.Range(0, validProps.Count);
GameObject prefab = validProps[index];
// Spawn at NPC root position, upright
spawnedProp = Instantiate(prefab, transform.position, Quaternion.identity);
// Do NOT parent the prop to the NPC - keeps it independent so NPC
// movement (if any residual) doesn't drag the prop around oddly.
// Position is snapped to the NPC's feet each frame in Update instead.
Debug.Log("[PolymorphEffect] Spawned prop " + prefab.name + " at " + transform.position);
if (showDebug)
{
Debug.DrawLine(transform.position, transform.position + Vector3.up * 2f, Color.green, duration);
}
}
// ------------------------------------------------------------------ //
// Update
// ------------------------------------------------------------------ //
private void Update()
{
// Keep the prop snapped to the NPC's feet in case anything moves it
if (spawnedProp != null)
{
spawnedProp.transform.position = transform.position;
}
timeRemaining -= Time.deltaTime;
if (timeRemaining <= 0f)
{
EndEffect();
}
}
// ------------------------------------------------------------------ //
// Cleanup
// ------------------------------------------------------------------ //
private void EndEffect()
{
Debug.Log("[PolymorphEffect] Ended on " + gameObject.name);
RestoreNPC();
Destroy(this);
}
private void RestoreNPC()
{
EnemyHealth enemyHealth = GetComponent<EnemyHealth>();
if (enemyHealth != null)
{
enemyHealth.isPolymorphed = false;
}
// Release the NPC state machine
NPCManager npcManager = GetComponent<NPCManager>();
if (npcManager != null)
{
npcManager.ExitStunnedState();
Debug.Log("[PolymorphEffect] Exited stunned state on NPCManager for " + gameObject.name);
}
// Restore gun if NPC is in combat
npcManager = GetComponent<NPCManager>();
if (npcManager != null && npcManager.hasEnteredAggro)
{
npcManager.SpawnGun();
}
// --- Destroy the prop ---
if (spawnedProp != null)
{
Destroy(spawnedProp);
spawnedProp = null;
}
// --- Restore NPC renderers ---
if (npcRenderers != null)
{
foreach (Renderer r in npcRenderers)
{
if (r != null)
{
r.enabled = true;
}
}
Debug.Log("[PolymorphEffect] Restored " + npcRenderers.Length + " renderers on " + gameObject.name);
}
// --- Resume NavMeshAgent ---
if (hadNavAgent && navAgent != null)
{
if (navAgent.isOnNavMesh)
{
navAgent.isStopped = false;
Debug.Log("[PolymorphEffect] Resumed NavMeshAgent on " + gameObject.name);
}
else
{
// Warp to nearest NavMesh point before resuming
NavMeshHit hit;
if (NavMesh.SamplePosition(transform.position, out hit, 3f, NavMesh.AllAreas))
{
navAgent.Warp(hit.position);
navAgent.isStopped = false;
Debug.Log("[PolymorphEffect] Warped and resumed NavMeshAgent on " + gameObject.name);
}
else
{
navAgent.isStopped = false;
Debug.LogWarning("[PolymorphEffect] No NavMesh near " + gameObject.name + " on restore");
}
}
}
// --- Restore Animator ---
if (hadAnimator && animator != null)
{
animator.speed = originalAnimatorSpeed;
Debug.Log("[PolymorphEffect] Restored Animator on " + gameObject.name);
}
}
private void OnDestroy()
{
EnemyHealth enemyHealth = GetComponent<EnemyHealth>();
if (enemyHealth != null)
{
enemyHealth.isPolymorphed = false;
}
// Safety net: restore everything if this component is destroyed externally
// (e.g. NPC dies while polymorphed)
if (spawnedProp != null)
{
Destroy(spawnedProp);
}
NPCManager npcManager = GetComponent<NPCManager>();
if (npcManager != null)
{
npcManager.ExitStunnedState();
}
if (npcRenderers != null)
{
foreach (Renderer r in npcRenderers)
{
if (r != null)
{
r.enabled = true;
}
}
}
if (hadNavAgent && navAgent != null)
{
navAgent.isStopped = false;
}
if (hadAnimator && animator != null)
{
animator.speed = originalAnimatorSpeed;
}
}
}
questeditorwindow.cs
Compiled
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
public class QuestEditorWindow : EditorWindow
{
private QuestData quest;
private Vector2 scrollPosition;
private QuestStep selectedStep;
private Vector2 panOffset = Vector2.zero;
private const float STEP_WIDTH = 250f;
private const float STEP_HEIGHT = 120f;
private GUIStyle stepStyle;
private GUIStyle selectedStepStyle;
[MenuItem("Window/Quest Editor")]
public static void OpenWindow()
{
GetWindow<QuestEditorWindow>("Quest Editor");
}
public static void OpenWindow(QuestData questData)
{
QuestEditorWindow window = GetWindow<QuestEditorWindow>("Quest Editor");
window.quest = questData;
}
private void OnEnable()
{
InitializeStyles();
}
private void InitializeStyles()
{
stepStyle = new GUIStyle();
stepStyle.normal.background = MakeTex(2, 2, new Color(0.3f, 0.4f, 0.5f, 1f));
stepStyle.border = new RectOffset(5, 5, 5, 5);
stepStyle.padding = new RectOffset(10, 10, 10, 10);
stepStyle.normal.textColor = Color.white;
stepStyle.fontSize = 12;
stepStyle.fontStyle = FontStyle.Bold;
stepStyle.alignment = TextAnchor.UpperLeft;
selectedStepStyle = new GUIStyle(stepStyle);
selectedStepStyle.normal.background = MakeTex(2, 2, new Color(0.2f, 0.6f, 0.8f, 1f));
}
private Texture2D MakeTex(int width, int height, Color col)
{
Color[] pix = new Color[width * height];
for (int i = 0; i < pix.Length; i++)
pix[i] = col;
Texture2D result = new Texture2D(width, height);
result.SetPixels(pix);
result.Apply();
return result;
}
private void OnGUI()
{
DrawToolbar();
if (quest == null)
{
EditorGUILayout.HelpBox("No Quest selected. Create one or select an existing one.", MessageType.Info);
return;
}
DrawGrid(20, 0.2f, Color.gray);
DrawGrid(100, 0.4f, Color.gray);
DrawConnections();
DrawSteps();
DrawStepInspector();
ProcessEvents(Event.current);
if (GUI.changed)
{
EditorUtility.SetDirty(quest);
}
}
private void DrawToolbar()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
quest = (QuestData)EditorGUILayout.ObjectField(quest, typeof(QuestData), false, GUILayout.Width(200));
if (GUILayout.Button("Add Step", EditorStyles.toolbarButton, GUILayout.Width(80)))
{
AddStep();
}
if (GUILayout.Button("Delete Step", EditorStyles.toolbarButton, GUILayout.Width(80)))
{
DeleteSelectedStep();
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
private void DrawGrid(float gridSpacing, float gridOpacity, Color gridColor)
{
int widthDivs = Mathf.CeilToInt(position.width / gridSpacing);
int heightDivs = Mathf.CeilToInt(position.height / gridSpacing);
Handles.BeginGUI();
Handles.color = new Color(gridColor.r, gridColor.g, gridColor.b, gridOpacity);
Vector3 newOffset = new Vector3(panOffset.x % gridSpacing, panOffset.y % gridSpacing, 0);
for (int i = 0; i < widthDivs; i++)
{
Handles.DrawLine(
new Vector3(gridSpacing * i, -gridSpacing, 0) + newOffset,
new Vector3(gridSpacing * i, position.height, 0f) + newOffset
);
}
for (int j = 0; j < heightDivs; j++)
{
Handles.DrawLine(
new Vector3(-gridSpacing, gridSpacing * j, 0) + newOffset,
new Vector3(position.width, gridSpacing * j, 0f) + newOffset
);
}
Handles.color = Color.white;
Handles.EndGUI();
}
private void DrawSteps()
{
if (quest == null || quest.steps == null) return;
for (int i = 0; i < quest.steps.Count; i++)
{
DrawStep(quest.steps[i], i);
}
}
private void DrawStep(QuestStep step, int index)
{
Rect stepRect = new Rect(
step.editorPosition.x + panOffset.x,
step.editorPosition.y + panOffset.y,
STEP_WIDTH,
STEP_HEIGHT
);
GUIStyle style = step == selectedStep ? selectedStepStyle : stepStyle;
GUI.Box(stepRect, "", style);
GUILayout.BeginArea(stepRect);
EditorGUILayout.LabelField($"Step {index + 1}: {step.stepID}", EditorStyles.boldLabel);
string preview = step.stepDescription.Length > 60
? step.stepDescription.Substring(0, 60) + "..."
: step.stepDescription;
EditorGUILayout.LabelField(preview, EditorStyles.wordWrappedMiniLabel);
EditorGUILayout.Space(5);
EditorGUILayout.LabelField($"Objectives: {step.objectives.Count}", EditorStyles.miniLabel);
GUILayout.EndArea();
}
private void DrawConnections()
{
if (quest == null || quest.steps == null || quest.steps.Count < 2) return;
Handles.BeginGUI();
for (int i = 0; i < quest.steps.Count - 1; i++)
{
QuestStep currentStep = quest.steps[i];
QuestStep nextStep = quest.steps[i + 1];
Vector3 startPos = new Vector3(
currentStep.editorPosition.x + STEP_WIDTH + panOffset.x,
currentStep.editorPosition.y + STEP_HEIGHT / 2 + panOffset.y,
0
);
Vector3 endPos = new Vector3(
nextStep.editorPosition.x + panOffset.x,
nextStep.editorPosition.y + STEP_HEIGHT / 2 + panOffset.y,
0
);
Handles.DrawBezier(
startPos,
endPos,
startPos + Vector3.right * 50f,
endPos + Vector3.left * 50f,
Color.cyan,
null,
2f
);
}
Handles.EndGUI();
}
private void DrawStepInspector()
{
if (selectedStep == null) return;
float inspectorWidth = 350f;
Rect inspectorRect = new Rect(position.width - inspectorWidth - 10, 40, inspectorWidth, position.height - 50);
GUILayout.BeginArea(inspectorRect, EditorStyles.helpBox);
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
EditorGUILayout.LabelField("Step Inspector", EditorStyles.boldLabel);
EditorGUILayout.Space();
selectedStep.stepID = EditorGUILayout.TextField("Step ID", selectedStep.stepID);
EditorGUILayout.LabelField("Description:");
selectedStep.stepDescription = EditorGUILayout.TextArea(selectedStep.stepDescription, GUILayout.Height(60));
EditorGUILayout.Space();
EditorGUILayout.LabelField("Objectives:", EditorStyles.boldLabel);
for (int i = 0; i < selectedStep.objectives.Count; i++)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField($"Objective {i + 1}", EditorStyles.miniBoldLabel);
selectedStep.objectives[i].type = (QuestObjective.ObjectiveType)EditorGUILayout.EnumPopup(
"Type",
selectedStep.objectives[i].type
);
selectedStep.objectives[i].description = EditorGUILayout.TextField(
"Description",
selectedStep.objectives[i].description
);
selectedStep.objectives[i].targetID = EditorGUILayout.TextField(
"Target ID",
selectedStep.objectives[i].targetID
);
selectedStep.objectives[i].requiredAmount = EditorGUILayout.IntField(
"Required Amount",
selectedStep.objectives[i].requiredAmount
);
if (GUILayout.Button("Remove Objective", GUILayout.Height(20)))
{
selectedStep.objectives.RemoveAt(i);
break;
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
}
if (GUILayout.Button("Add Objective"))
{
selectedStep.objectives.Add(new QuestObjective());
}
EditorGUILayout.EndScrollView();
GUILayout.EndArea();
}
private void ProcessEvents(Event e)
{
switch (e.type)
{
case EventType.MouseDown:
if (e.button == 0)
{
SelectStep(e.mousePosition);
}
else if (e.button == 2)
{
panOffset += e.delta;
}
break;
case EventType.MouseDrag:
if (e.button == 0 && selectedStep != null)
{
selectedStep.editorPosition += e.delta;
GUI.changed = true;
}
else if (e.button == 2)
{
panOffset += e.delta;
GUI.changed = true;
}
Repaint();
break;
}
}
private void SelectStep(Vector2 mousePosition)
{
selectedStep = null;
if (quest == null || quest.steps == null) return;
for (int i = quest.steps.Count - 1; i >= 0; i--)
{
Rect stepRect = new Rect(
quest.steps[i].editorPosition.x + panOffset.x,
quest.steps[i].editorPosition.y + panOffset.y,
STEP_WIDTH,
STEP_HEIGHT
);
if (stepRect.Contains(mousePosition))
{
selectedStep = quest.steps[i];
GUI.changed = true;
break;
}
}
}
private void AddStep()
{
if (quest == null) return;
QuestStep newStep = new QuestStep();
newStep.stepID = "step_" + quest.steps.Count;
newStep.stepDescription = "New step description";
newStep.editorPosition = new Vector2(100 + (quest.steps.Count * 300), 100) - panOffset;
quest.steps.Add(newStep);
selectedStep = newStep;
EditorUtility.SetDirty(quest);
}
private void DeleteSelectedStep()
{
if (selectedStep == null || quest == null) return;
quest.steps.Remove(selectedStep);
selectedStep = null;
EditorUtility.SetDirty(quest);
}
}
[CustomEditor(typeof(QuestData))]
public class QuestDataEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
EditorGUILayout.Space(10);
if (GUILayout.Button("Open Quest Editor", GUILayout.Height(30)))
{
QuestEditorWindow.OpenWindow((QuestData)target);
}
}
}
#endif
barrelremovalminigame.cs
Compiled
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// Minigame for REMOVING barrel attachments - unscrew and drag away
/// </summary>
public class BarrelRemovalMinigame : AttachmentMinigameBase
{
[Header("Removal Settings")]
[SerializeField] private float unscrewDistance = 2f;
[SerializeField] private float maxUnscrewPerPull = 0.25f;
[SerializeField] private float unscrewRotationSpeed = 180f;
[SerializeField] private float unscrewMoveDistance = 0.05f;
[SerializeField] private Vector3 unscrewMoveDirection = Vector3.forward;
[SerializeField] private float minDistanceToMoveAway = 0.1f;
[Header("Camera Zoom Settings")]
[SerializeField] private float zoomAmount = 0.7f;
[SerializeField] private float zoomSpeed = 2f;
private Camera mainCamera;
private Vector3 originalCameraPosition;
private bool isZooming = false;
private bool isZoomingOut = false;
private Vector3 targetCameraPosition;
private enum RemovalState { Unscrewing, MovingAway, Complete }
private RemovalState currentState = RemovalState.Unscrewing;
private float unscrewProgress = 0f;
private float totalUnscrewDistance = 0f;
private float currentUnscrewPullDistance = 0f;
private Vector3 lastMousePosition;
private List<GameObject> weaponPartsToReEnable = new List<GameObject>();
private Vector3 socketWorldPosition;
private bool isDraggingPart = false;
private Vector3 grabbedPartDragStart;
private Vector3 grabbedPartStartPos;
protected override void Awake()
{
base.Awake();
// Add collider if needed
Collider col = GetComponent<Collider>();
if (col == null)
{
col = gameObject.AddComponent<BoxCollider>();
}
}
public override void StartMinigame()
{
base.StartMinigame();
mainCamera = minigameCamera != null ? minigameCamera : Camera.main;
if (mainCamera == null)
{
Debug.LogError("BarrelRemovalMinigame: No camera found!");
return;
}
originalCameraPosition = mainCamera.transform.position;
if (targetSocket != null)
{
socketWorldPosition = targetSocket.position;
}
currentState = RemovalState.Unscrewing;
// Zoom camera to barrel
ZoomToBarrel();
Debug.Log("Barrel removal started. Drag UP to unscrew the barrel.");
}
/// <summary>
/// Set weapon parts that will be re-enabled when barrel is removed
/// </summary>
public void SetWeaponPartsToReEnable(Transform weaponTransform, List<string> partPaths)
{
weaponPartsToReEnable.Clear();
if (partPaths == null || partPaths.Count == 0 || weaponTransform == null)
return;
foreach (var partPath in partPaths)
{
if (string.IsNullOrEmpty(partPath))
continue;
Transform partTransform = weaponTransform.Find(partPath);
if (partTransform == null)
partTransform = FindChildByName(weaponTransform, partPath);
if (partTransform != null)
{
weaponPartsToReEnable.Add(partTransform.gameObject);
Debug.Log($"Will re-enable: {partTransform.name}");
}
}
}
private Transform FindChildByName(Transform parent, string name)
{
foreach (Transform child in parent)
{
if (child.name == name)
return child;
Transform result = FindChildByName(child, name);
if (result != null)
return result;
}
return null;
}
private void ZoomToBarrel()
{
if (mainCamera == null || targetSocket == null) return;
Vector3 directionToBarrel = (targetSocket.position - mainCamera.transform.position).normalized;
float distanceToBarrel = Vector3.Distance(mainCamera.transform.position, targetSocket.position);
targetCameraPosition = mainCamera.transform.position + (directionToBarrel * distanceToBarrel * zoomAmount);
isZooming = true;
}
protected override void Update()
{
base.Update();
if (isComplete) return;
// Handle camera zoom
if ((isZooming || isZoomingOut) && mainCamera != null)
{
mainCamera.transform.position = Vector3.Lerp(
mainCamera.transform.position,
targetCameraPosition,
Time.deltaTime * zoomSpeed
);
if (Vector3.Distance(mainCamera.transform.position, targetCameraPosition) < 0.01f)
{
mainCamera.transform.position = targetCameraPosition;
isZooming = false;
isZoomingOut = false;
}
}
// Animate barrel moving out as it's unscrewed
if (currentState == RemovalState.Unscrewing && unscrewProgress > 0f)
{
Vector3 moveOffset = transform.TransformDirection(unscrewMoveDirection) * unscrewMoveDistance * unscrewProgress;
transform.position = Vector3.Lerp(transform.position, targetSocket.position + moveOffset, Time.deltaTime * 5f);
}
switch (currentState)
{
case RemovalState.Unscrewing:
HandleUnscrewing();
break;
case RemovalState.MovingAway:
HandleMovingAway();
break;
}
}
void HandleUnscrewing()
{
// Start a new pull
if (Input.GetMouseButtonDown(0))
{
lastMousePosition = Input.mousePosition;
currentUnscrewPullDistance = 0f;
Debug.Log("Started new unscrew pull");
}
// Continue pulling UP
if (Input.GetMouseButton(0))
{
Vector3 currentMousePos = Input.mousePosition;
Vector3 mouseDelta = currentMousePos - lastMousePosition;
// Only count UPWARD movement
float upwardMovement = mouseDelta.y / Screen.height * 10f;
if (upwardMovement > 0)
{
float maxDistanceThisPull = unscrewDistance * maxUnscrewPerPull;
if (currentUnscrewPullDistance < maxDistanceThisPull)
{
float distanceToAdd = Mathf.Min(upwardMovement, maxDistanceThisPull - currentUnscrewPullDistance);
currentUnscrewPullDistance += distanceToAdd;
totalUnscrewDistance += distanceToAdd;
unscrewProgress = Mathf.Clamp01(totalUnscrewDistance / unscrewDistance);
// Rotate as we unscrew
float rotationAmount = distanceToAdd * unscrewRotationSpeed;
transform.Rotate(Vector3.forward, -rotationAmount, Space.Self);
// Visual feedback
Color progressColor = Color.Lerp(Color.red, Color.green, unscrewProgress);
SetColor(progressColor);
Debug.Log($"Unscrew progress: {unscrewProgress * 100f:F0}%");
}
else
{
Debug.Log($"Unscrew pull limit reached! Release and pull again");
}
// Check if complete
if (unscrewProgress >= 1f)
{
Debug.Log("Barrel unscrewed! Now drag it away from the weapon.");
currentState = RemovalState.MovingAway;
SetColor(Color.yellow);
}
}
lastMousePosition = currentMousePos;
}
// Released mouse - reset for next pull
if (Input.GetMouseButtonUp(0))
{
if (currentUnscrewPullDistance > 0)
{
Debug.Log($"Unscrew pull complete: {currentUnscrewPullDistance:F2} units. Total progress: {unscrewProgress * 100f:F0}%");
}
currentUnscrewPullDistance = 0f;
}
}
void HandleMovingAway()
{
// Start dragging
if (Input.GetMouseButtonDown(0))
{
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit[] hits = Physics.RaycastAll(ray, 1000f);
foreach (var hit in hits)
{
if (hit.collider.gameObject == gameObject || hit.collider.transform.IsChildOf(transform))
{
isDraggingPart = true;
grabbedPartDragStart = Input.mousePosition;
grabbedPartStartPos = transform.position;
Debug.Log($"Grabbed barrel: {gameObject.name}");
break;
}
}
}
// Continue dragging
if (isDraggingPart && Input.GetMouseButton(0))
{
Vector3 currentMousePos = Input.mousePosition;
Vector3 mouseDelta = currentMousePos - grabbedPartDragStart;
Vector3 worldDelta = mainCamera.ScreenToWorldPoint(new Vector3(mouseDelta.x, mouseDelta.y, mainCamera.WorldToScreenPoint(grabbedPartStartPos).z))
- mainCamera.ScreenToWorldPoint(new Vector3(0, 0, mainCamera.WorldToScreenPoint(grabbedPartStartPos).z));
transform.position = grabbedPartStartPos + worldDelta;
// Check distance from socket
float distanceFromSocket = Vector3.Distance(transform.position, socketWorldPosition);
// Visual feedback
Color feedbackColor = distanceFromSocket >= minDistanceToMoveAway ? Color.green : Color.yellow;
SetColor(feedbackColor);
}
// Release
if (Input.GetMouseButtonUp(0) && isDraggingPart)
{
float distanceFromSocket = Vector3.Distance(transform.position, socketWorldPosition);
if (distanceFromSocket >= minDistanceToMoveAway)
{
Debug.Log($"Barrel moved far enough away. Removal complete!");
CompleteMinigame();
}
else
{
Debug.Log($"Not far enough away. Move it further!");
}
isDraggingPart = false;
}
}
protected override void CompleteMinigame()
{
SetColor(Color.green);
// Re-enable weapon parts (default barrel)
if (weaponPartsToReEnable != null && weaponPartsToReEnable.Count > 0)
{
Debug.Log($"Re-enabling {weaponPartsToReEnable.Count} weapon part(s)");
foreach (var part in weaponPartsToReEnable)
{
if (part != null)
{
Debug.Log($" Re-enabling: {part.name}");
part.SetActive(true);
}
}
}
// Zoom camera back
if (mainCamera != null)
{
targetCameraPosition = originalCameraPosition;
isZoomingOut = true;
}
StartCoroutine(CompleteAfterZoomOut());
}
public override void CancelMinigame()
{
if (mainCamera != null)
{
mainCamera.transform.position = originalCameraPosition;
isZooming = false;
isZoomingOut = false;
}
base.CancelMinigame();
}
private System.Collections.IEnumerator CompleteAfterZoomOut()
{
while (isZoomingOut)
{
yield return null;
}
base.CompleteMinigame();
}
void OnDrawGizmos()
{
if (targetSocket != null)
{
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(targetSocket.position, minDistanceToMoveAway);
Gizmos.color = Color.red;
Vector3 moveOffset = transform.TransformDirection(unscrewMoveDirection) * unscrewMoveDistance;
Gizmos.DrawLine(transform.position, transform.position + moveOffset);
}
}
}