diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot.meta b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot.meta new file mode 100644 index 000000000..3bb118d07 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f11bacc0ef1a67244944dcffc1fbbb9e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallBoost.asset b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallBoost.asset new file mode 100644 index 000000000..bf0911415 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallBoost.asset @@ -0,0 +1,67 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 89795172, guid: 951ce57ad999ac1f040a4dceb5f8b763, type: 3} + m_Name: escBallBoost + m_EditorClassIdentifier: + targetType: + assemblyQualifiedName: EntityStates.NemToolbot.BallBoost, Starstorm2, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null + serializedFieldsCollection: + serializedFields: + - fieldName: baseDamageCoefficient + fieldValue: + stringValue: 4 + objectValue: {fileID: 0} + - fieldName: awayForceMagnitude + fieldValue: + stringValue: 1000 + objectValue: {fileID: 0} + - fieldName: upwardForceMagnitude + fieldValue: + stringValue: 500 + objectValue: {fileID: 0} + - fieldName: hitPauseDuration + fieldValue: + stringValue: 0.1 + objectValue: {fileID: 0} + - fieldName: recoilAmplitude + fieldValue: + stringValue: 1 + objectValue: {fileID: 0} + - fieldName: massThresholdForKnockback + fieldValue: + stringValue: 250 + objectValue: {fileID: 0} + - fieldName: knockbackForce + fieldValue: + stringValue: 3000 + objectValue: {fileID: 0} + - fieldName: startSoundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: endSoundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: impactSoundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: startEffectPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: impactEffectPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallBoost.asset.meta b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallBoost.asset.meta new file mode 100644 index 000000000..d7f4b9e75 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallBoost.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b08200c6c3da88f45a29d00654286425 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallMainState.asset b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallMainState.asset new file mode 100644 index 000000000..d9cdac999 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallMainState.asset @@ -0,0 +1,47 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 89795172, guid: 951ce57ad999ac1f040a4dceb5f8b763, type: 3} + m_Name: escBallMainState + m_EditorClassIdentifier: + targetType: + assemblyQualifiedName: EntityStates.NemToolbot.BallMainState, Starstorm2, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null + serializedFieldsCollection: + serializedFields: + - fieldName: moveSpeedMultiplier + fieldValue: + stringValue: 1.6 + objectValue: {fileID: 0} + - fieldName: ballAirControl + fieldValue: + stringValue: 0.15 + objectValue: {fileID: 0} + - fieldName: accelerationMultiplier + fieldValue: + stringValue: 0.6 + objectValue: {fileID: 0} + - fieldName: momentumDamping + fieldValue: + stringValue: 0.98 + objectValue: {fileID: 0} + - fieldName: enterSoundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: loopSoundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: exitSoundString + fieldValue: + stringValue: + objectValue: {fileID: 0} diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallMainState.asset.meta b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallMainState.asset.meta new file mode 100644 index 000000000..ec8b6eac5 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallMainState.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 39f7b37f3bd74544494d7465ae397e90 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallSlam.asset b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallSlam.asset new file mode 100644 index 000000000..5d8f0bd9e --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallSlam.asset @@ -0,0 +1,79 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 89795172, guid: 951ce57ad999ac1f040a4dceb5f8b763, type: 3} + m_Name: escBallSlam + m_EditorClassIdentifier: + targetType: + assemblyQualifiedName: EntityStates.NemToolbot.BallSlam, Starstorm2, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null + serializedFieldsCollection: + serializedFields: + - fieldName: airControl + fieldValue: + stringValue: 0.15 + objectValue: {fileID: 0} + - fieldName: minimumDuration + fieldValue: + stringValue: 0.15 + objectValue: {fileID: 0} + - fieldName: verticalAcceleration + fieldValue: + stringValue: -80 + objectValue: {fileID: 0} + - fieldName: exitSlowdownCoefficient + fieldValue: + stringValue: 0.3 + objectValue: {fileID: 0} + - fieldName: exitVerticalVelocity + fieldValue: + stringValue: 8 + objectValue: {fileID: 0} + - fieldName: blastRadius + fieldValue: + stringValue: 12 + objectValue: {fileID: 0} + - fieldName: blastDamageCoefficient + fieldValue: + stringValue: 5 + objectValue: {fileID: 0} + - fieldName: blastProcCoefficient + fieldValue: + stringValue: 1 + objectValue: {fileID: 0} + - fieldName: blastForce + fieldValue: + stringValue: 2000 + objectValue: {fileID: 0} + - fieldName: maxDuration + fieldValue: + stringValue: 10 + objectValue: {fileID: 0} + - fieldName: blastBonusForce + fieldValue: + stringValue: (0.0, 0.0, 0.0) + objectValue: {fileID: 0} + - fieldName: enterSoundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: blastEffectPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: blastImpactEffectPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: slamEffectPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallSlam.asset.meta b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallSlam.asset.meta new file mode 100644 index 000000000..0ff8ca64c --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escBallSlam.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9ef4aa2f76bfec846a904e387dcf0f0c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escDeployCharges.asset b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escDeployCharges.asset new file mode 100644 index 000000000..7ce8f0b74 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escDeployCharges.asset @@ -0,0 +1,47 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 89795172, guid: 951ce57ad999ac1f040a4dceb5f8b763, type: 3} + m_Name: escDeployCharges + m_EditorClassIdentifier: + targetType: + assemblyQualifiedName: EntityStates.NemToolbot.DeployCharges, Starstorm2, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null + serializedFieldsCollection: + serializedFields: + - fieldName: damageCoefficient + fieldValue: + stringValue: 4 + objectValue: {fileID: 0} + - fieldName: baseDuration + fieldValue: + stringValue: 0.5 + objectValue: {fileID: 0} + - fieldName: chargeSpawnRadius + fieldValue: + stringValue: 2 + objectValue: {fileID: 0} + - fieldName: force + fieldValue: + stringValue: 500 + objectValue: {fileID: 0} + - fieldName: soundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: muzzleFlashPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: chargeProjectilePrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escDeployCharges.asset.meta b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escDeployCharges.asset.meta new file mode 100644 index 000000000..cd401d250 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escDeployCharges.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 20e7eb8c3c7bc2340bd89f722b762314 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escDispatchWeapon.asset b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escDispatchWeapon.asset new file mode 100644 index 000000000..9e93844eb --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escDispatchWeapon.asset @@ -0,0 +1,19 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 89795172, guid: 951ce57ad999ac1f040a4dceb5f8b763, type: 3} + m_Name: escDispatchWeapon + m_EditorClassIdentifier: + targetType: + assemblyQualifiedName: EntityStates.NemToolbot.DispatchWeapon, Starstorm2, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null + serializedFieldsCollection: + serializedFields: [] diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escDispatchWeapon.asset.meta b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escDispatchWeapon.asset.meta new file mode 100644 index 000000000..5766b1cb3 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escDispatchWeapon.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 30ee2fded33a907469f5f2315b763b65 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireGrapplingHook.asset b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireGrapplingHook.asset new file mode 100644 index 000000000..d6ce3c392 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireGrapplingHook.asset @@ -0,0 +1,43 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 89795172, guid: 951ce57ad999ac1f040a4dceb5f8b763, type: 3} + m_Name: escFireGrapplingHook + m_EditorClassIdentifier: + targetType: + assemblyQualifiedName: EntityStates.NemToolbot.FireGrapplingHook, Starstorm2, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null + serializedFieldsCollection: + serializedFields: + - fieldName: damageCoefficient + fieldValue: + stringValue: 1 + objectValue: {fileID: 0} + - fieldName: fireSoundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: disconnectSoundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: muzzleflashEffectPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: muzzleString + fieldValue: + stringValue: Muzzle + objectValue: {fileID: 0} + - fieldName: maxDuration + fieldValue: + stringValue: 8 + objectValue: {fileID: 0} diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireGrapplingHook.asset.meta b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireGrapplingHook.asset.meta new file mode 100644 index 000000000..38d7d28d4 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireGrapplingHook.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c4cd3a69c494a954595004d00416116b +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireGrenadeLauncher.asset b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireGrenadeLauncher.asset new file mode 100644 index 000000000..b4bd07142 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireGrenadeLauncher.asset @@ -0,0 +1,55 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 89795172, guid: 951ce57ad999ac1f040a4dceb5f8b763, type: 3} + m_Name: escFireGrenadeLauncher + m_EditorClassIdentifier: + targetType: + assemblyQualifiedName: EntityStates.NemToolbot.FireGrenadeLauncher, Starstorm2, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null + serializedFieldsCollection: + serializedFields: + - fieldName: damageCoefficient + fieldValue: + stringValue: 3 + objectValue: {fileID: 0} + - fieldName: force + fieldValue: + stringValue: 1500 + objectValue: {fileID: 0} + - fieldName: baseDuration + fieldValue: + stringValue: 0.6 + objectValue: {fileID: 0} + - fieldName: recoilAmplitude + fieldValue: + stringValue: 1.5 + objectValue: {fileID: 0} + - fieldName: spreadBloomValue + fieldValue: + stringValue: 1 + objectValue: {fileID: 0} + - fieldName: muzzleString + fieldValue: + stringValue: Muzzle + objectValue: {fileID: 0} + - fieldName: soundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: muzzleFlashPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: projectilePrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireGrenadeLauncher.asset.meta b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireGrenadeLauncher.asset.meta new file mode 100644 index 000000000..a9dad56df --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireGrenadeLauncher.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 80366aacdd64bc445ac58ca9e00d011a +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireRapidLaser.asset b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireRapidLaser.asset new file mode 100644 index 000000000..be82ffc94 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireRapidLaser.asset @@ -0,0 +1,75 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 89795172, guid: 951ce57ad999ac1f040a4dceb5f8b763, type: 3} + m_Name: escFireRapidLaser + m_EditorClassIdentifier: + targetType: + assemblyQualifiedName: EntityStates.NemToolbot.FireRapidLaser, Starstorm2, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null + serializedFieldsCollection: + serializedFields: + - fieldName: damageCoefficient + fieldValue: + stringValue: 0.8 + objectValue: {fileID: 0} + - fieldName: procCoefficient + fieldValue: + stringValue: 0.7 + objectValue: {fileID: 0} + - fieldName: baseFireInterval + fieldValue: + stringValue: 0.12 + objectValue: {fileID: 0} + - fieldName: force + fieldValue: + stringValue: 100 + objectValue: {fileID: 0} + - fieldName: recoilAmplitude + fieldValue: + stringValue: 0.4 + objectValue: {fileID: 0} + - fieldName: spreadBloomValue + fieldValue: + stringValue: 0.3 + objectValue: {fileID: 0} + - fieldName: maxDistance + fieldValue: + stringValue: 60 + objectValue: {fileID: 0} + - fieldName: bulletRadius + fieldValue: + stringValue: 0.4 + objectValue: {fileID: 0} + - fieldName: baseDuration + fieldValue: + stringValue: 0.1 + objectValue: {fileID: 0} + - fieldName: muzzleString + fieldValue: + stringValue: Muzzle + objectValue: {fileID: 0} + - fieldName: soundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: muzzleFlashPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: tracerEffectPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: hitEffectPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireRapidLaser.asset.meta b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireRapidLaser.asset.meta new file mode 100644 index 000000000..c983e821b --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireRapidLaser.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3eaacfd2c8bfb0d4d93acf715f43469b +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireShotgun.asset b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireShotgun.asset new file mode 100644 index 000000000..20a2a71fc --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireShotgun.asset @@ -0,0 +1,79 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 89795172, guid: 951ce57ad999ac1f040a4dceb5f8b763, type: 3} + m_Name: escFireShotgun + m_EditorClassIdentifier: + targetType: + assemblyQualifiedName: EntityStates.NemToolbot.FireShotgun, Starstorm2, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null + serializedFieldsCollection: + serializedFields: + - fieldName: damageCoefficient + fieldValue: + stringValue: 1.2 + objectValue: {fileID: 0} + - fieldName: procCoefficient + fieldValue: + stringValue: 0.5 + objectValue: {fileID: 0} + - fieldName: baseDuration + fieldValue: + stringValue: 0.6 + objectValue: {fileID: 0} + - fieldName: force + fieldValue: + stringValue: 300 + objectValue: {fileID: 0} + - fieldName: recoilAmplitude + fieldValue: + stringValue: 3 + objectValue: {fileID: 0} + - fieldName: spreadBloomValue + fieldValue: + stringValue: 1.5 + objectValue: {fileID: 0} + - fieldName: pelletCount + fieldValue: + stringValue: 6 + objectValue: {fileID: 0} + - fieldName: spreadAngle + fieldValue: + stringValue: 10 + objectValue: {fileID: 0} + - fieldName: maxDistance + fieldValue: + stringValue: 12 + objectValue: {fileID: 0} + - fieldName: bulletRadius + fieldValue: + stringValue: 0.3 + objectValue: {fileID: 0} + - fieldName: muzzleString + fieldValue: + stringValue: Muzzle + objectValue: {fileID: 0} + - fieldName: soundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: muzzleFlashPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: tracerEffectPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: hitEffectPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireShotgun.asset.meta b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireShotgun.asset.meta new file mode 100644 index 000000000..fd89a7c1d --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireShotgun.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2087d7396b7403046829339e9f7c9d8b +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireSniperLaser.asset b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireSniperLaser.asset new file mode 100644 index 000000000..466a404b1 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireSniperLaser.asset @@ -0,0 +1,71 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 89795172, guid: 951ce57ad999ac1f040a4dceb5f8b763, type: 3} + m_Name: escFireSniperLaser + m_EditorClassIdentifier: + targetType: + assemblyQualifiedName: EntityStates.NemToolbot.FireSniperLaser, Starstorm2, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null + serializedFieldsCollection: + serializedFields: + - fieldName: damageCoefficient + fieldValue: + stringValue: 6 + objectValue: {fileID: 0} + - fieldName: procCoefficient + fieldValue: + stringValue: 1 + objectValue: {fileID: 0} + - fieldName: baseDuration + fieldValue: + stringValue: 0.8 + objectValue: {fileID: 0} + - fieldName: force + fieldValue: + stringValue: 2500 + objectValue: {fileID: 0} + - fieldName: recoilAmplitude + fieldValue: + stringValue: 4 + objectValue: {fileID: 0} + - fieldName: spreadBloomValue + fieldValue: + stringValue: 2 + objectValue: {fileID: 0} + - fieldName: maxDistance + fieldValue: + stringValue: 1000 + objectValue: {fileID: 0} + - fieldName: bulletRadius + fieldValue: + stringValue: 0.5 + objectValue: {fileID: 0} + - fieldName: muzzleString + fieldValue: + stringValue: Muzzle + objectValue: {fileID: 0} + - fieldName: soundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: muzzleFlashPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: tracerEffectPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: hitEffectPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireSniperLaser.asset.meta b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireSniperLaser.asset.meta new file mode 100644 index 000000000..9baddb054 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escFireSniperLaser.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2fafdf1caab072549bc916215a343ca2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escRangeFinder.asset b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escRangeFinder.asset new file mode 100644 index 000000000..b7a88307a --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escRangeFinder.asset @@ -0,0 +1,39 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 89795172, guid: 951ce57ad999ac1f040a4dceb5f8b763, type: 3} + m_Name: escRangeFinder + m_EditorClassIdentifier: + targetType: + assemblyQualifiedName: EntityStates.NemToolbot.RangeFinder, Starstorm2, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null + serializedFieldsCollection: + serializedFields: + - fieldName: maxRange + fieldValue: + stringValue: 200 + objectValue: {fileID: 0} + - fieldName: muzzleString + fieldValue: + stringValue: Muzzle + objectValue: {fileID: 0} + - fieldName: enterSoundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: exitSoundString + fieldValue: + stringValue: + objectValue: {fileID: 0} + - fieldName: laserPrefab + fieldValue: + stringValue: + objectValue: {fileID: 0} diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escRangeFinder.asset.meta b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escRangeFinder.asset.meta new file mode 100644 index 000000000..737fcc329 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escRangeFinder.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: be09324759323af40a755dd7240b9b13 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escStanceSwapToBall.asset b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escStanceSwapToBall.asset new file mode 100644 index 000000000..c36b0cd34 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escStanceSwapToBall.asset @@ -0,0 +1,23 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 89795172, guid: 951ce57ad999ac1f040a4dceb5f8b763, type: 3} + m_Name: escStanceSwapToBall + m_EditorClassIdentifier: + targetType: + assemblyQualifiedName: EntityStates.NemToolbot.StanceSwapToBall, Starstorm2, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null + serializedFieldsCollection: + serializedFields: + - fieldName: enterSoundString + fieldValue: + stringValue: + objectValue: {fileID: 0} diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escStanceSwapToBall.asset.meta b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escStanceSwapToBall.asset.meta new file mode 100644 index 000000000..51a97f1df --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escStanceSwapToBall.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ba8942b7aa0849646befbcd9b6a36b45 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escStanceSwapToDeployed.asset b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escStanceSwapToDeployed.asset new file mode 100644 index 000000000..01d4b4274 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escStanceSwapToDeployed.asset @@ -0,0 +1,23 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 89795172, guid: 951ce57ad999ac1f040a4dceb5f8b763, type: 3} + m_Name: escStanceSwapToDeployed + m_EditorClassIdentifier: + targetType: + assemblyQualifiedName: EntityStates.NemToolbot.StanceSwapToDeployed, Starstorm2, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null + serializedFieldsCollection: + serializedFields: + - fieldName: enterSoundString + fieldValue: + stringValue: + objectValue: {fileID: 0} diff --git a/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escStanceSwapToDeployed.asset.meta b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escStanceSwapToDeployed.asset.meta new file mode 100644 index 000000000..57af220f9 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/AssetStorm/InDev/Characters/NemToolbot/escStanceSwapToDeployed.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 642c5b79f5002d441b5d3187d481da02 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Components/NemToolbot.meta b/SS2-Project/Assets/Starstorm2/Components/NemToolbot.meta new file mode 100644 index 000000000..eec4242e7 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Components/NemToolbot.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e2564ce86268ba3419ed1efbda687d4c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Components/NemToolbot/NemToolbotController.cs b/SS2-Project/Assets/Starstorm2/Components/NemToolbot/NemToolbotController.cs new file mode 100644 index 000000000..f92dd5efb --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Components/NemToolbot/NemToolbotController.cs @@ -0,0 +1,257 @@ +using RoR2; +using UnityEngine; +using UnityEngine.Networking; + +namespace SS2.Components +{ + public class NemToolbotController : NetworkBehaviour + { + public enum WeaponType : byte + { + Shotgun = 0, + RapidLaser = 1, + GrenadeLauncher = 2, + SniperLaser = 3 + } + + // Range thresholds(meters) for weapon selection + public static float shotgunMaxRange = 3f; + public static float rapidLaserMaxRange = 6f; + public static float grenadeLauncherMaxRange = 9f; + + // Ammo pool configuration + public static int shotgunMaxAmmo = 10; + public static int rapidLaserMaxAmmo = 50; + public static int grenadeLauncherMaxAmmo = 10; + public static int sniperLaserMaxAmmo = 10; + + // Regen rates (ammo per second, only while in ball form) + public static float shotgunRegenRate = 1f; + public static float rapidLaserRegenRate = 5f; + public static float grenadeLauncherRegenRate = 1f; + public static float sniperLaserRegenRate = 1f; + + [SyncVar(hook = nameof(OnWeaponChanged))] + private WeaponType _currentWeapon = WeaponType.RapidLaser; + + [SyncVar(hook = nameof(OnFormChanged))] + private bool _isBallForm; + + [SyncVar] + private int _shotgunAmmo; + [SyncVar] + private int _rapidLaserAmmo; + [SyncVar] + private int _grenadeLauncherAmmo; + [SyncVar] + private int _sniperLaserAmmo; + + // Sub-frame regen accumulators (server-only, not synced) + private float shotgunRegenAccumulator; + private float rapidLaserRegenAccumulator; + private float grenadeLauncherRegenAccumulator; + private float sniperLaserRegenAccumulator; + + private CharacterBody characterBody; + private CharacterMotor characterMotor; + + public WeaponType currentWeapon => _currentWeapon; + public bool isBallForm => _isBallForm; + + private void Awake() + { + if (!TryGetComponent(out characterBody)) + { + Debug.LogError("NemToolbotController: Failed to get CharacterBody on " + gameObject.name); + } + if (!TryGetComponent(out characterMotor)) + { + Debug.LogError("NemToolbotController: Failed to get CharacterMotor on " + gameObject.name); + } + + // Initialize ammo to max + _shotgunAmmo = shotgunMaxAmmo; + _rapidLaserAmmo = rapidLaserMaxAmmo; + _grenadeLauncherAmmo = grenadeLauncherMaxAmmo; + _sniperLaserAmmo = sniperLaserMaxAmmo; + } + + /// + /// Maps a raycast distance to the appropriate weapon type. + /// + public static WeaponType GetWeaponFromRange(float distance) + { + if (distance < shotgunMaxRange) + return WeaponType.Shotgun; + if (distance < rapidLaserMaxRange) + return WeaponType.RapidLaser; + if (distance < grenadeLauncherMaxRange) + return WeaponType.GrenadeLauncher; + return WeaponType.SniperLaser; + } + + /// + /// Sets the current weapon. Server-only. + /// + [Server] + public void SetWeapon(WeaponType weapon) + { + Debug.Log($"[NemToolbot] SetWeapon: {_currentWeapon} -> {weapon}"); + OnWeaponChanged(weapon); + _currentWeapon = weapon; + } + + /// + /// Sets ball form state. Server-only. + /// + [Server] + public void SetBallForm(bool ballForm) + { + Debug.Log($"[NemToolbot] SetBallForm: {_isBallForm} -> {ballForm}"); + OnFormChanged(ballForm); + _isBallForm = ballForm; + } + + #region Ammo System + + /// + /// Returns the max ammo for a given weapon type. + /// + public static int GetMaxAmmo(WeaponType weapon) + { + switch (weapon) + { + case WeaponType.Shotgun: return shotgunMaxAmmo; + case WeaponType.RapidLaser: return rapidLaserMaxAmmo; + case WeaponType.GrenadeLauncher: return grenadeLauncherMaxAmmo; + case WeaponType.SniperLaser: return sniperLaserMaxAmmo; + default: return 0; + } + } + + /// + /// Returns the current ammo for a given weapon type. Client-readable. + /// + public int GetAmmo(WeaponType weapon) + { + switch (weapon) + { + case WeaponType.Shotgun: return _shotgunAmmo; + case WeaponType.RapidLaser: return _rapidLaserAmmo; + case WeaponType.GrenadeLauncher: return _grenadeLauncherAmmo; + case WeaponType.SniperLaser: return _sniperLaserAmmo; + default: return 0; + } + } + + /// + /// Returns true if the given weapon has at least 1 ammo. Client-readable. + /// + public bool HasAmmo(WeaponType weapon) + { + return GetAmmo(weapon) > 0; + } + + /// + /// Attempts to consume 1 ammo from the given weapon's pool. Server-only. + /// Returns true if ammo was consumed, false if the pool was empty. + /// + [Server] + public bool TryConsumeAmmo(WeaponType weapon) + { + switch (weapon) + { + case WeaponType.Shotgun: + if (_shotgunAmmo <= 0) return false; + _shotgunAmmo--; + return true; + case WeaponType.RapidLaser: + if (_rapidLaserAmmo <= 0) return false; + _rapidLaserAmmo--; + return true; + case WeaponType.GrenadeLauncher: + if (_grenadeLauncherAmmo <= 0) return false; + _grenadeLauncherAmmo--; + return true; + case WeaponType.SniperLaser: + if (_sniperLaserAmmo <= 0) return false; + _sniperLaserAmmo--; + return true; + default: + return false; + } + } + + /// + /// Regenerates ammo for all weapons. Called by BallMainState each tick. + /// Server-only. Uses float accumulators for sub-frame precision. + /// + public void RegenAllAmmo(float deltaTime) + { + if (!NetworkServer.active) + return; + + RegenWeaponAmmo(ref _shotgunAmmo, shotgunMaxAmmo, shotgunRegenRate, ref shotgunRegenAccumulator, deltaTime); + RegenWeaponAmmo(ref _rapidLaserAmmo, rapidLaserMaxAmmo, rapidLaserRegenRate, ref rapidLaserRegenAccumulator, deltaTime); + RegenWeaponAmmo(ref _grenadeLauncherAmmo, grenadeLauncherMaxAmmo, grenadeLauncherRegenRate, ref grenadeLauncherRegenAccumulator, deltaTime); + RegenWeaponAmmo(ref _sniperLaserAmmo, sniperLaserMaxAmmo, sniperLaserRegenRate, ref sniperLaserRegenAccumulator, deltaTime); + } + + private static void RegenWeaponAmmo(ref int currentAmmo, int maxAmmo, float regenRate, ref float accumulator, float deltaTime) + { + if (currentAmmo >= maxAmmo) + { + accumulator = 0f; + return; + } + + accumulator += regenRate * deltaTime; + if (accumulator >= 1f) + { + int toAdd = Mathf.FloorToInt(accumulator); + accumulator -= toAdd; + currentAmmo = Mathf.Min(currentAmmo + toAdd, maxAmmo); + } + } + + /// + /// Resets all regen accumulators. Call when exiting ball form. + /// + public void ResetRegenAccumulators() + { + shotgunRegenAccumulator = 0f; + rapidLaserRegenAccumulator = 0f; + grenadeLauncherRegenAccumulator = 0f; + sniperLaserRegenAccumulator = 0f; + } + + #endregion + + /// + /// Returns a damage multiplier based on current velocity relative to base move speed. + /// Used by ball form attacks to scale damage with momentum. + /// + public float GetDamageMultiplierFromSpeed() + { + if (characterBody == null || characterMotor == null) + return 1f; + + float currentSpeed = characterMotor.velocity.magnitude; + float baseSpeed = characterBody.baseMoveSpeed; + if (baseSpeed <= 0f) + return 1f; + + return Mathf.Max(1f, currentSpeed / baseSpeed); + } + + private void OnWeaponChanged(WeaponType newWeapon) + { + _currentWeapon = newWeapon; + } + + private void OnFormChanged(bool newBallForm) + { + _isBallForm = newBallForm; + } + } +} diff --git a/SS2-Project/Assets/Starstorm2/Components/NemToolbot/NemToolbotController.cs.meta b/SS2-Project/Assets/Starstorm2/Components/NemToolbot/NemToolbotController.cs.meta new file mode 100644 index 000000000..8392388b7 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Components/NemToolbot/NemToolbotController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 96211b05081c54e49b5e6d88ee802112 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Components/NemToolbot/NemToolbotGrappleController.cs b/SS2-Project/Assets/Starstorm2/Components/NemToolbot/NemToolbotGrappleController.cs new file mode 100644 index 000000000..15fad0257 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Components/NemToolbot/NemToolbotGrappleController.cs @@ -0,0 +1,640 @@ +using System; +using EntityStates; +using EntityStates.NemToolbot; +using UnityEngine; +using UnityEngine.Networking; +using RoR2; +using RoR2.Projectile; + +namespace SS2.Components +{ + /// + /// Projectile component that drives NemToolbot's grappling hook lifecycle. + /// Based on Loader's ProjectileGrappleController with identical feel. + /// + /// State pipeline: + /// FlyState --> GripState (heavy target or world) --> ReturnState + /// |-> YankState (light enemy) --> ReturnState + /// |-> ReturnState (missed / max distance) + /// + /// Changes from Loader's version: + /// - Casts to FireGrapplingHook instead of FireHook + /// - Configurable ESM customName (hookStateMachineName) + /// - TryGetComponent everywhere with Debug.LogError on failure + /// - Configurable stock deduction slot + /// + [RequireComponent(typeof(EntityStateMachine))] + [RequireComponent(typeof(ProjectileController))] + [RequireComponent(typeof(ProjectileSimple))] + [RequireComponent(typeof(ProjectileStickOnImpact))] + public class NemToolbotGrappleController : MonoBehaviour + { + private struct OwnerInfo + { + public readonly GameObject gameObject; + public readonly CharacterBody characterBody; + public readonly CharacterMotor characterMotor; + public readonly EntityStateMachine stateMachine; + public readonly bool hasEffectiveAuthority; + + public OwnerInfo(GameObject ownerGameObject, string hookESMName) + { + this = default(OwnerInfo); + gameObject = ownerGameObject; + if (!gameObject) + { + return; + } + + if (!gameObject.TryGetComponent(out CharacterBody body)) + { + Debug.LogError("NemToolbotGrappleController: Owner missing CharacterBody on " + gameObject.name); + } + characterBody = body; + + if (!gameObject.TryGetComponent(out CharacterMotor motor)) + { + Debug.LogError("NemToolbotGrappleController: Owner missing CharacterMotor on " + gameObject.name); + } + characterMotor = motor; + + hasEffectiveAuthority= Util.HasEffectiveAuthority(gameObject); + + EntityStateMachine[] esms = gameObject.GetComponents(); + for (int i = 0; i < esms.Length; i++) + { + if (esms[i].customName == hookESMName) + { + stateMachine = esms[i]; + break; + } + } + + if (!stateMachine) + { + Debug.LogError("NemToolbotGrappleController: Owner missing ESM with customName '" + hookESMName + "' on " + gameObject.name); + } + } + } + + #region Inner States + + /// + /// Shared foundation for all grapple hook states. Validates owner each tick, + /// updates spatial data, and auto-retracts when owner exits firing state. + /// + private class BaseState : EntityStates.BaseState + { + protected NemToolbotGrappleController grappleController; + protected Vector3 aimOrigin; + protected Vector3 position; + + protected bool ownerValid { get; private set; } + protected ref OwnerInfo owner => ref grappleController.owner; + + private void UpdatePositions() + { + aimOrigin = grappleController.owner.characterBody.aimOrigin; + position = base.transform.position + base.transform.up * grappleController.normalOffset; + } + + public override void OnEnter() + { + base.OnEnter(); + if (!base.gameObject.TryGetComponent(out NemToolbotGrappleController controller)) + { + Debug.LogError("NemToolbotGrappleController.BaseState: Missing NemToolbotGrappleController on " + base.gameObject.name); + return; + } + grappleController = controller; + ownerValid = (bool)grappleController && (bool)grappleController.owner.gameObject; + if (ownerValid) + { + UpdatePositions(); + } + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + if (ownerValid) + { + ownerValid &= grappleController.owner.gameObject; + if (ownerValid) + { + UpdatePositions(); + FixedUpdateBehavior(); + } + } + if (NetworkServer.active && !ownerValid) + { + ownerValid = false; + EntityState.Destroy(base.gameObject); + } + } + + protected virtual void FixedUpdateBehavior() + { + if (base.isAuthority && !grappleController.OwnerIsInFiringState()) + { + outer.SetNextState(new ReturnState()); + } + } + + protected Ray GetOwnerAimRay() + { + if (!owner.characterBody) + { + return default(Ray); + } + return owner.characterBody.inputBank.GetAimRay(); + } + } + + /// + /// Hook is traveling outward. On impact, transitions to GripState (heavy/world) + /// or YankState (light enemy). Auto-retracts if max distance reached. + /// + private class FlyState : BaseState + { + private float duration; + + public override void OnEnter() + { + base.OnEnter(); + if (grappleController != null) + { + duration = grappleController.maxTravelDistance / grappleController.projectileSimple.velocity; + } + } + + protected override void FixedUpdateBehavior() + { + base.FixedUpdateBehavior(); + if (!base.isAuthority) + { + return; + } + + if (grappleController.projectileStickOnImpactController.stuck) + { + EntityState nextState = null; + + if ((bool)grappleController.projectileStickOnImpactController.stuckBody) + { + if (grappleController.projectileStickOnImpactController.stuckBody.TryGetComponent(out Rigidbody stuckRb)) + { + if (stuckRb.mass < grappleController.yankMassLimit) + { + stuckRb.TryGetComponent(out CharacterBody stuckCharBody); + if (!stuckCharBody + || !stuckCharBody.isPlayerControlled + || stuckCharBody.teamComponent.teamIndex != base.projectileController.teamFilter.teamIndex + || FriendlyFireManager.ShouldDirectHitProceed(stuckCharBody.healthComponent, base.projectileController.teamFilter.teamIndex)) + { + nextState = new YankState(); + } + } + } + } + + if (nextState == null) + { + nextState = new GripState(); + } + + DeductOwnerStock(); + outer.SetNextState(nextState); + } + else if (duration <= base.fixedAge) + { + outer.SetNextState(new ReturnState()); + } + } + + private void DeductOwnerStock() + { + if (!base.ownerValid || !base.owner.hasEffectiveAuthority) + { + return; + } + + if (base.owner.gameObject.TryGetComponent(out SkillLocator skillLocator)) + { + GenericSkill skill = grappleController.GetDeductionSkill(skillLocator); + if ((bool)skill) + { + skill.DeductStock(1); + } + } + else + { + Debug.LogError("NemToolbotGrappleController.FlyState: Owner missing SkillLocator on " + base.owner.gameObject.name); + } + } + } + + /// + /// Shared base for GripState and YankState. Tracks distance and handles + /// release conditions: unstuck, near-break, button release, state exit. + /// + private class BaseGripState : BaseState + { + protected float currentDistance; + + public override void OnEnter() + { + base.OnEnter(); + currentDistance = Vector3.Distance(aimOrigin, position); + } + + protected override void FixedUpdateBehavior() + { + base.FixedUpdateBehavior(); + currentDistance = Vector3.Distance(aimOrigin, position); + if (base.isAuthority) + { + bool hookDetached = !grappleController.projectileStickOnImpactController.stuck; + bool arrivedAtHook = currentDistance < grappleController.nearBreakDistance; + bool ownerLeftFiringState = !grappleController.OwnerIsInFiringState(); + + if (!base.owner.stateMachine + || !((base.owner.stateMachine.state as BaseSkillState)?.IsKeyDownAuthority() ?? false) + || ownerLeftFiringState + || arrivedAtHook + || hookDetached) + { + outer.SetNextState(new ReturnState()); + } + } + } + } + + /// + /// Pulls the PLAYER toward the hook point (grapple swing). Identical force model + /// to Loader's GripState: base acceleration + look steering + move input + escape force. + /// + private class GripState : BaseGripState + { + private float lastDistance; + + public override void OnEnter() + { + base.OnEnter(); + lastDistance = Vector3.Distance(aimOrigin, position); + if (base.ownerValid) + { + if ((bool)base.owner.characterMotor) + { + Vector3 direction = GetOwnerAimRay().direction; + Vector3 velocity = base.owner.characterMotor.velocity; + velocity = ((Vector3.Dot(velocity, direction) < 0f) ? Vector3.zero : Vector3.Project(velocity, direction)); + velocity += direction * grappleController.initialLookImpulse; + velocity += base.owner.characterMotor.moveDirection * grappleController.initiallMoveImpulse; + base.owner.characterMotor.velocity = velocity; + } + } + } + + protected override void FixedUpdateBehavior() + { + base.FixedUpdateBehavior(); + float accel = grappleController.acceleration; + + if (currentDistance > lastDistance) + { + accel *= grappleController.escapeForceMultiplier; + } + lastDistance = currentDistance; + + if (base.owner.hasEffectiveAuthority && (bool)base.owner.characterMotor && (bool)base.owner.characterBody) + { + Ray ownerAimRay = GetOwnerAimRay(); + Vector3 hookDirection = (base.transform.position - base.owner.characterBody.aimOrigin).normalized; + Vector3 force = hookDirection * accel; + + float time = Mathf.Clamp01(base.fixedAge / grappleController.lookAccelerationRampUpDuration); + float rampUpFactor = grappleController.lookAccelerationRampUpCurve.Evaluate(time); + float alignmentFactor = Util.Remap(Vector3.Dot(ownerAimRay.direction, hookDirection), -1f, 1f, 1f, 0f); + force += ownerAimRay.direction * (grappleController.lookAcceleration * rampUpFactor * alignmentFactor); + + force += base.owner.characterMotor.moveDirection * grappleController.moveAcceleration; + + base.owner.characterMotor.ApplyForce(force * (base.owner.characterMotor.mass * GetDeltaTime()), alwaysApply: true, disableAirControlUntilCollision: true); + } + } + } + + /// + /// Pulls a LIGHT ENEMY toward the player. Identical to Loader's YankState: + /// IDisplacementReceiver pull with delay, brief owner hover. + /// + private class YankState : BaseGripState + { + public static float yankSpeed; + public static float delayBeforeYanking; + public static float hoverTimeLimit = 0.5f; + + private CharacterBody stuckBody; + + public override void OnEnter() + { + base.OnEnter(); + stuckBody = grappleController.projectileStickOnImpactController.stuckBody; + } + + protected override void FixedUpdateBehavior() + { + base.FixedUpdateBehavior(); + if (!stuckBody) + { + return; + } + + if (Util.HasEffectiveAuthority(stuckBody.gameObject)) + { + Vector3 pullDirection = aimOrigin - position; + if (stuckBody.TryGetComponent(out IDisplacementReceiver displacementReceiver) && base.fixedAge >= delayBeforeYanking) + { + displacementReceiver.AddDisplacement(pullDirection * (yankSpeed * GetDeltaTime())); + } + } + + if (base.owner.hasEffectiveAuthority && (bool)base.owner.characterMotor && base.fixedAge < hoverTimeLimit) + { + Vector3 velocity = base.owner.characterMotor.velocity; + if (velocity.y < 0f) + { + velocity.y = 0f; + base.owner.characterMotor.velocity = velocity; + } + } + } + } + + /// + /// Hook retracts back to the player. Detaches, disables collider, + /// accelerates toward aimOrigin, and self-destructs on overshoot. + /// + private class ReturnState : BaseState + { + private float returnSpeedAcceleration = 240f; + private float returnSpeed; + + public override void OnEnter() + { + base.OnEnter(); + if (base.ownerValid) + { + returnSpeed = grappleController.projectileSimple.velocity; + returnSpeedAcceleration = returnSpeed * 2f; + } + + if (NetworkServer.active && (bool)grappleController) + { + grappleController.projectileStickOnImpactController.Detach(); + grappleController.projectileStickOnImpactController.ignoreCharacters = true; + grappleController.projectileStickOnImpactController.ignoreWorld = true; + } + + if (base.gameObject.TryGetComponent(out Collider collider)) + { + collider.enabled = false; + } + } + + protected override void FixedUpdateBehavior() + { + base.FixedUpdateBehavior(); + if (!base.rigidbody) + { + return; + } + + returnSpeed += returnSpeedAcceleration * GetDeltaTime(); + base.rigidbody.velocity = (aimOrigin - position).normalized * returnSpeed; + + if (NetworkServer.active) + { + Vector3 endPosition = position + base.rigidbody.velocity * GetDeltaTime(); + if (HGMath.Overshoots(position, endPosition, aimOrigin)) + { + EntityState.Destroy(base.gameObject); + } + } + } + } + + #endregion + + #region Controller Fields + + private ProjectileController projectileController; + private ProjectileStickOnImpact projectileStickOnImpactController; + private ProjectileSimple projectileSimple; + + /// + /// The EntityState type the owner must be in for the hook to remain active. + /// Set to FireGrapplingHook in the inspector. If the owner exits this state, the hook retracts. + /// + public SerializableEntityStateType ownerHookStateType; + + /// + /// Name of the EntityStateMachine on the owner that runs the hook firing state. + /// Default "Hook". Must match the ESM customName on the NemToolbot character body. + /// + [Tooltip("ESM customName on the owner body that runs the hook firing state.")] + public string hookStateMachineName = "Hook"; + + /// + /// Which skill slot to deduct stock from on successful hook impact. + /// + public enum DeductSlot + { + Primary, + Secondary, + Utility, + Special, + None + } + + [Tooltip("Which skill slot to deduct stock from when the hook sticks.")] + public DeductSlot deductStockSlot = DeductSlot.Secondary; + + public float acceleration; + public float lookAcceleration = 4f; + public float lookAccelerationRampUpDuration = 0.25f; + public float initialLookImpulse = 5f; + public float initiallMoveImpulse = 5f; + public float moveAcceleration = 4f; + + public string enterSoundString; + public string exitSoundString; + public string hookDistanceRTPCstring; + public float minHookDistancePitchModifier; + public float maxHookDistancePitchModifier; + + public AnimationCurve lookAccelerationRampUpCurve; + public Transform ropeEndTransform; + public string muzzleStringOnBody = "MuzzleLeft"; + + [Tooltip("Minimum distance before the hook auto-detaches (arrival).")] + public float nearBreakDistance; + + [Tooltip("Maximum travel distance for the hook.")] + public float maxTravelDistance; + + public float escapeForceMultiplier = 2f; + public float normalOffset = 1f; + public float yankMassLimit; + + private Type resolvedOwnerHookStateType; + private OwnerInfo owner; + private uint soundID; + + #endregion + + #region Lifecycle + + private void Awake() + { + if (!TryGetComponent(out projectileStickOnImpactController)) + { + Debug.LogError("NemToolbotGrappleController: Missing ProjectileStickOnImpact on " + gameObject.name); + } + if (!TryGetComponent(out projectileController)) + { + Debug.LogError("NemToolbotGrappleController: Missing ProjectileController on " + gameObject.name); + } + if (!TryGetComponent(out projectileSimple)) + { + Debug.LogError("NemToolbotGrappleController: Missing ProjectileSimple on " + gameObject.name); + } + + resolvedOwnerHookStateType = ownerHookStateType.stateType; + + if ((bool)ropeEndTransform) + { + soundID = Util.PlaySound(enterSoundString, ropeEndTransform.gameObject); + } + } + + private void Start() + { + if (projectileController != null && projectileController.owner != null) + { + owner = new OwnerInfo(projectileController.owner, hookStateMachineName); + AssignHookReferenceToBodyStateMachine(); + } + else + { + Debug.LogError("NemToolbotGrappleController: projectileController or owner is null on Start for " + gameObject.name); + } + } + + private void FixedUpdate() + { + if ((bool)ropeEndTransform) + { + float in_value = Util.Remap( + (ropeEndTransform.transform.position - base.transform.position).magnitude, + minHookDistancePitchModifier, + maxHookDistancePitchModifier, + 0f, 100f); + AkSoundEngine.SetRTPCValueByPlayingID(hookDistanceRTPCstring, in_value, soundID); + } + } + + private void OnDestroy() + { + if ((bool)ropeEndTransform) + { + Util.PlaySound(exitSoundString, ropeEndTransform.gameObject); + UnityEngine.Object.Destroy(ropeEndTransform.gameObject); + } + else + { + AkSoundEngine.StopPlayingID(soundID); + } + } + + #endregion + + #region Private Methods + + /// + /// Links this hook projectile back to the owner's FireGrapplingHook state + /// and parents the rope end to the owner's muzzle bone. + /// + private void AssignHookReferenceToBodyStateMachine() + { + if ((bool)owner.stateMachine && owner.stateMachine.state is FireGrapplingHook fireGrapplingHook) + { + fireGrapplingHook.SetHookReference(base.gameObject); + } + + if (!owner.gameObject.TryGetComponent(out ModelLocator modelLocator)) + { + Debug.LogError("NemToolbotGrappleController: Owner missing ModelLocator on " + owner.gameObject.name); + return; + } + + Transform modelTransform = modelLocator.modelTransform; + if (!modelTransform) + { + return; + } + + if (modelTransform.TryGetComponent(out ChildLocator childLocator)) + { + Transform muzzle = childLocator.FindChild(muzzleStringOnBody); + if ((bool)muzzle && (bool)ropeEndTransform) + { + ropeEndTransform.SetParent(muzzle, worldPositionStays: false); + } + } + else + { + Debug.LogError("NemToolbotGrappleController: Owner model missing ChildLocator on " + modelTransform.name); + } + } + + private bool OwnerIsInFiringState() + { + if ((bool)owner.stateMachine) + { + return owner.stateMachine.state.GetType() == resolvedOwnerHookStateType; + } + return false; + } + + private GenericSkill GetDeductionSkill(SkillLocator skillLocator) + { + switch (deductStockSlot) + { + case DeductSlot.Primary: + return skillLocator.primary; + case DeductSlot.Secondary: + return skillLocator.secondary; + case DeductSlot.Utility: + return skillLocator.utility; + case DeductSlot.Special: + return skillLocator.special; + default: + return null; + } + } + + /// + /// Configures a projectile's EntityStateMachine to use this controller's inner FlyState. + /// Call this when setting up the grapple hook projectile prefab in code. + /// + public static void ConfigureProjectileESM(EntityStateMachine esm) + { + esm.initialStateType = new SerializableEntityStateType(typeof(FlyState)); + esm.mainStateType = new SerializableEntityStateType(typeof(FlyState)); + } + + #endregion + } +} diff --git a/SS2-Project/Assets/Starstorm2/Components/NemToolbot/NemToolbotGrappleController.cs.meta b/SS2-Project/Assets/Starstorm2/Components/NemToolbot/NemToolbotGrappleController.cs.meta new file mode 100644 index 000000000..55b28debf --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Components/NemToolbot/NemToolbotGrappleController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 10f3ec3e4a77aa44d94cefee11c06a5a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Modules/Characters/Survivors/NemToolbot.cs b/SS2-Project/Assets/Starstorm2/Modules/Characters/Survivors/NemToolbot.cs new file mode 100644 index 000000000..88fa3d687 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/Characters/Survivors/NemToolbot.cs @@ -0,0 +1,251 @@ +using MSU; +using RoR2; +using UnityEngine; +using UnityEngine.AddressableAssets; +using UnityEngine.Networking; +using RoR2.ContentManagement; +using RoR2.Projectile; +using SS2.Components; +using R2API; +using EntityStates.NemToolbot; + +#if DEBUG +namespace SS2.Survivors +{ + public sealed class NemToolbot : SS2Survivor + { + public override SS2AssetRequest AssetRequest => SS2Assets.LoadAssetAsync("acNemToolbot", SS2Bundle.Indev); + + private GameObject grappleHookProjectile; + private GameObject grenadeProjectile; + private GameObject deployChargeProjectile; + + public override void Initialize() + { + ModifyPrefab(); + CreateProjectiles(); + AssignEffects(); + } + + private void ModifyPrefab() + { + if (!CharacterPrefab.TryGetComponent(out CharacterBody cb)) + { + Debug.LogError("NemToolbot.ModifyPrefab: Failed to get CharacterBody on " + CharacterPrefab.name); + return; + } + + cb._defaultCrosshairPrefab = Addressables.LoadAssetAsync("RoR2/Base/UI/StandardCrosshair.prefab").WaitForCompletion(); + cb.preferredPodPrefab = Addressables.LoadAssetAsync("RoR2/Base/Toolbot/RoboCratePod.prefab").WaitForCompletion(); + + if (cb.TryGetComponent(out ModelLocator modelLocator) && modelLocator.modelTransform != null) + { + if (modelLocator.modelTransform.TryGetComponent(out FootstepHandler footstepHandler)) + { + footstepHandler.footstepDustPrefab = Addressables.LoadAssetAsync("RoR2/Base/Common/VFX/GenericFootstepDust.prefab").WaitForCompletion(); + } + } + } + + private static void EnsureNetworkIdentity(GameObject prefab) + { + if (!prefab.GetComponent()) + { + prefab.AddComponent(); + } + } + + private void CreateProjectiles() + { + CreateGrappleHookProjectile(); + CreateGrenadeProjectile(); + CreateDeployChargeProjectile(); + } + + private void CreateGrappleHookProjectile() + { + GameObject loaderHook = Addressables.LoadAssetAsync("RoR2/Base/Loader/LoaderHook.prefab").WaitForCompletion(); + grappleHookProjectile = loaderHook.InstantiateClone("NemToolbotGrappleHook", true); + EnsureNetworkIdentity(grappleHookProjectile); + + // Null out ropeEndTransform before destroy to prevent vanilla OnDestroy + // from deleting the RopeEnd child we need for our controller + ProjectileGrappleController loaderGrapple = grappleHookProjectile.GetComponent(); + if (loaderGrapple != null) + { + loaderGrapple.ropeEndTransform = null; + Object.DestroyImmediate(loaderGrapple); + } + + // Reconfigure the projectile's ESM before adding our controller + // (RequireComponent won't duplicate it since one already exists from the clone) + if (grappleHookProjectile.TryGetComponent(out EntityStateMachine esm)) + { + NemToolbotGrappleController.ConfigureProjectileESM(esm); + } + else + { + Debug.LogError("NemToolbot: Cloned LoaderHook missing EntityStateMachine."); + } + + // Add our grapple controller and configure + NemToolbotGrappleController grappleController = grappleHookProjectile.AddComponent(); + grappleController.ownerHookStateType = new EntityStates.SerializableEntityStateType(typeof(FireGrapplingHook)); + grappleController.hookStateMachineName = "Hook"; + grappleController.acceleration = 45f; + grappleController.lookAcceleration = 35f; + grappleController.lookAccelerationRampUpDuration = 0.5f; + grappleController.initialLookImpulse = 0f; + grappleController.initiallMoveImpulse = 15f; + grappleController.moveAcceleration = 5f; + grappleController.nearBreakDistance = 1f; + grappleController.maxTravelDistance = 80f; + grappleController.escapeForceMultiplier = 4f; + grappleController.normalOffset = 1f; + grappleController.yankMassLimit = 0f; + grappleController.muzzleStringOnBody = "MuzzleLeft"; + grappleController.deductStockSlot = NemToolbotGrappleController.DeductSlot.Secondary; + grappleController.lookAccelerationRampUpCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f); + grappleController.enterSoundString = "Play_loader_m2_travel_loop"; + grappleController.exitSoundString = "Stop_loader_m2_travel_loop"; + grappleController.hookDistanceRTPCstring = "loaderM2_grappleRemain"; + grappleController.minHookDistancePitchModifier = 3f; + grappleController.maxHookDistancePitchModifier = 80f; + + // Find and assign the rope end transform from the cloned prefab + Transform ropeEnd = grappleHookProjectile.transform.Find("RopeEnd"); + if (ropeEnd != null) + { + grappleController.ropeEndTransform = ropeEnd; + } + else + { + Debug.LogError("NemToolbot: Cloned LoaderHook missing RopeEnd child transform."); + } + } + + private void CreateGrenadeProjectile() + { + GameObject toolbotGrenade = Addressables.LoadAssetAsync("RoR2/Base/Toolbot/ToolbotGrenadeLauncherProjectile.prefab").WaitForCompletion(); + grenadeProjectile = toolbotGrenade.InstantiateClone("NemToolbotGrenadeProjectile", true); + EnsureNetworkIdentity(grenadeProjectile); + + // Enable gravity for arc trajectory + if (grenadeProjectile.TryGetComponent(out Rigidbody rb)) + { + rb.useGravity = true; + } + else + { + Debug.LogError("NemToolbot: Cloned grenade missing Rigidbody."); + } + + // Set explosion VFX + if (grenadeProjectile.TryGetComponent(out ProjectileImpactExplosion pie)) + { + pie.impactEffect = Addressables.LoadAssetAsync("RoR2/Base/Toolbot/OmniExplosionVFXToolbotQuick.prefab").WaitForCompletion(); + } + } + + private void CreateDeployChargeProjectile() + { + GameObject toolbotGrenade = Addressables.LoadAssetAsync("RoR2/Base/Toolbot/ToolbotGrenadeLauncherProjectile.prefab").WaitForCompletion(); + deployChargeProjectile = toolbotGrenade.InstantiateClone("NemToolbotDeployCharge", true); + EnsureNetworkIdentity(deployChargeProjectile); + + // Stationary: no forward movement, no gravity + if (deployChargeProjectile.TryGetComponent(out ProjectileSimple ps)) + { + ps.desiredForwardSpeed = 0f; + ps.lifetime = 4f; + } + else + { + Debug.LogError("NemToolbot: Cloned deploy charge missing ProjectileSimple."); + } + + if (deployChargeProjectile.TryGetComponent(out Rigidbody rb)) + { + rb.useGravity = false; + rb.isKinematic = true; + } + + // Timed detonation: explode after lifetime, not on contact + if (deployChargeProjectile.TryGetComponent(out ProjectileImpactExplosion pie)) + { + pie.destroyOnWorld = false; + pie.destroyOnEnemy = false; + pie.timerAfterImpact = false; + pie.lifetime = 4f; + pie.impactEffect = Addressables.LoadAssetAsync("RoR2/Base/Toolbot/OmniExplosionVFXToolbotQuick.prefab").WaitForCompletion(); + } + else + { + Debug.LogError("NemToolbot: Cloned deploy charge missing ProjectileImpactExplosion."); + } + } + + private void AssignEffects() + { + // Cache shared loads + GameObject muzzleflashFMJ = Addressables.LoadAssetAsync("RoR2/Base/Commando/MuzzleflashFMJ.prefab").WaitForCompletion(); + GameObject hitsparkBandit = Addressables.LoadAssetAsync("RoR2/Base/Bandit2/HitsparkBandit.prefab").WaitForCompletion(); + GameObject loaderGroundSlam = Addressables.LoadAssetAsync("RoR2/Base/Loader/LoaderGroundSlam.prefab").WaitForCompletion(); + + // Shotgun + FireShotgun.muzzleFlashPrefab = muzzleflashFMJ; + FireShotgun.tracerEffectPrefab = Addressables.LoadAssetAsync("RoR2/Base/Bandit2/TracerBandit2Shotgun.prefab").WaitForCompletion(); + FireShotgun.hitEffectPrefab = hitsparkBandit; + + // Rapid laser + FireRapidLaser.muzzleFlashPrefab = muzzleflashFMJ; + FireRapidLaser.tracerEffectPrefab = Addressables.LoadAssetAsync("RoR2/Base/CaptainDefenseMatrix/TracerCaptainDefenseMatrix.prefab").WaitForCompletion(); + FireRapidLaser.hitEffectPrefab = hitsparkBandit; + + // Sniper laser + FireSniperLaser.muzzleFlashPrefab = Addressables.LoadAssetAsync("RoR2/DLC1/Railgunner/MuzzleflashRailgun.prefab").WaitForCompletion(); + FireSniperLaser.tracerEffectPrefab = Addressables.LoadAssetAsync("RoR2/Base/Toolbot/TracerToolbotRebar.prefab").WaitForCompletion(); + FireSniperLaser.hitEffectPrefab = Addressables.LoadAssetAsync("RoR2/DLC1/Railgunner/ImpactRailgun.prefab").WaitForCompletion(); + + // Grenade launcher + FireGrenadeLauncher.muzzleFlashPrefab = muzzleflashFMJ; + FireGrenadeLauncher.projectilePrefab = grenadeProjectile; + + // Deploy charges + DeployCharges.muzzleFlashPrefab = muzzleflashFMJ; + DeployCharges.chargeProjectilePrefab = deployChargeProjectile; + + // Grappling hook + FireGrapplingHook.muzzleflashEffectPrefab = Addressables.LoadAssetAsync("RoR2/Base/Loader/MuzzleflashLoader.prefab").WaitForCompletion(); + FireGrapplingHook.projectilePrefab = grappleHookProjectile; + + // Ball boost + BallBoost.startEffectPrefab = Addressables.LoadAssetAsync("RoR2/Base/Toolbot/ImpactToolbotDash.prefab").WaitForCompletion(); + BallBoost.impactEffectPrefab = Addressables.LoadAssetAsync("RoR2/Base/Toolbot/ImpactToolbotDashLarge.prefab").WaitForCompletion(); + + // Ball slam + BallSlam.blastEffectPrefab = loaderGroundSlam; + BallSlam.blastImpactEffectPrefab = Addressables.LoadAssetAsync("RoR2/Base/Loader/OmniImpactVFXLoader.prefab").WaitForCompletion(); + BallSlam.slamEffectPrefab = loaderGroundSlam; + } + + public override void ModifyContentPack(ContentPack contentPack) + { + base.ModifyContentPack(contentPack); + contentPack.projectilePrefabs.Add(new GameObject[] + { + grappleHookProjectile, + grenadeProjectile, + deployChargeProjectile, + }); + } + + // NOTE: Must return true (or use SS2Config beta gate) for Initialize() + // and ModifyContentPack() to run. MSU skips both when IsAvailable=false. + public override bool IsAvailable(ContentPack contentPack) + { + return false; + } + } +} +#endif diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot.meta b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot.meta new file mode 100644 index 000000000..5be4d49b9 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 949db2a536025984db3b04a98fe40183 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallBoost.cs b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallBoost.cs new file mode 100644 index 000000000..d86bbee2f --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallBoost.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using RoR2; +using SS2.Components; +using UnityEngine; + +namespace EntityStates.NemToolbot +{ + /// + /// Ball form primary (grounded). Boosts forward at high speed with an OverlapAttack, + /// dealing speed-scaled damage to enemies in the path. Similar to Mul-T's ToolbotDash. + /// If used while airborne, immediately dispatches to BallSlam instead. + /// + public class BallBoost : BaseSkillState + { + [SerializeField] + public float baseDuration = 1.5f; + + [SerializeField] + public float speedMultiplier = 2.5f; + + public static float baseDamageCoefficient = 4f; + public static float awayForceMagnitude = 1000f; + public static float upwardForceMagnitude = 500f; + public static float hitPauseDuration = 0.1f; + public static float recoilAmplitude = 1f; + public static float massThresholdForKnockback = 250f; + public static float knockbackForce = 3000f; + + public static string startSoundString = ""; + public static string endSoundString = ""; + public static string impactSoundString = ""; + public static GameObject startEffectPrefab; + public static GameObject impactEffectPrefab; + + private NemToolbotController controller; + private float duration; + private float hitPauseTimer; + private Vector3 idealDirection; + private OverlapAttack attack; + private bool inHitPause; + private List victimsStruck = new List(); + + public override void OnEnter() + { + base.OnEnter(); + + // If airborne, dispatch to BallSlam instead + if (isAuthority && characterMotor != null && !characterMotor.isGrounded) + { + outer.SetNextState(new BallSlam()); + return; + } + + if (!gameObject.TryGetComponent(out controller)) + { + Debug.LogError("NemToolbot BallBoost: Failed to get NemToolbotController on " + gameObject.name); + } + + duration = baseDuration; + + if (isAuthority && inputBank != null) + { + idealDirection = inputBank.aimDirection; + idealDirection.y = 0f; + idealDirection.Normalize(); + } + + if (characterDirection != null) + { + characterDirection.forward = idealDirection; + } + + if (startEffectPrefab != null && characterBody != null) + { + EffectManager.SpawnEffect(startEffectPrefab, new EffectData + { + origin = characterBody.corePosition + }, transmit: false); + } + + Util.PlaySound(startSoundString, gameObject); + + if (characterBody != null) + { + characterBody.isSprinting = true; + } + + // Build overlap attack + HitBoxGroup hitBoxGroup = null; + Transform modelTransform = GetModelTransform(); + if (modelTransform != null) + { + hitBoxGroup = Array.Find(modelTransform.GetComponents(), (HitBoxGroup element) => element.groupName == "Charge"); + } + + attack = new OverlapAttack(); + attack.attacker = gameObject; + attack.inflictor = gameObject; + attack.teamIndex = GetTeam(); + attack.damage = baseDamageCoefficient * damageStat; + attack.hitEffectPrefab = impactEffectPrefab; + attack.forceVector = Vector3.up * upwardForceMagnitude; + attack.pushAwayForce = awayForceMagnitude; + attack.hitBoxGroup = hitBoxGroup; + attack.damageType.damageSource = DamageSource.Utility; + attack.isCrit = RollCrit(); + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + + if (fixedAge >= duration) + { + if (isAuthority) + { + outer.SetNextStateToMain(); + } + return; + } + + if (!isAuthority) + return; + + if (!inHitPause) + { + UpdateDirection(); + + if (characterDirection != null) + { + characterDirection.moveVector = idealDirection; + } + + if (characterMotor != null && !characterMotor.disableAirControlUntilCollision) + { + characterMotor.rootMotion += GetIdealVelocity() * GetDeltaTime(); + } + + // Scale damage by speed + float speedMultFromVelocity = controller != null ? controller.GetDamageMultiplierFromSpeed() : 1f; + attack.damage = damageStat * (baseDamageCoefficient * speedMultFromVelocity); + + if (attack.Fire(victimsStruck)) + { + Util.PlaySound(impactSoundString, gameObject); + inHitPause = true; + hitPauseTimer = hitPauseDuration; + AddRecoil(-0.5f * recoilAmplitude, -0.5f * recoilAmplitude, -0.5f * recoilAmplitude, 0.5f * recoilAmplitude); + } + } + else + { + if (characterMotor != null) + { + characterMotor.velocity = Vector3.zero; + } + hitPauseTimer -= GetDeltaTime(); + if (hitPauseTimer < 0f) + { + inHitPause = false; + } + } + } + + public override void OnExit() + { + Util.PlaySound(endSoundString, gameObject); + + if (characterMotor != null && !characterMotor.disableAirControlUntilCollision) + { + characterMotor.velocity += GetIdealVelocity(); + } + + if (characterBody != null) + { + characterBody.isSprinting = false; + } + + base.OnExit(); + } + + private void UpdateDirection() + { + if (inputBank != null) + { + Vector2 moveInput = Util.Vector3XZToVector2XY(inputBank.moveVector); + if (moveInput != Vector2.zero) + { + moveInput.Normalize(); + idealDirection = new Vector3(moveInput.x, 0f, moveInput.y).normalized; + } + } + } + + private Vector3 GetIdealVelocity() + { + if (characterDirection == null || characterBody == null) + return Vector3.zero; + + return characterDirection.forward * characterBody.moveSpeed * speedMultiplier; + } + + public override InterruptPriority GetMinimumInterruptPriority() + { + return InterruptPriority.Frozen; + } + } +} diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallBoost.cs.meta b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallBoost.cs.meta new file mode 100644 index 000000000..b12197928 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallBoost.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aa129d756bacbcf4c911777337d1580d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallMainState.cs b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallMainState.cs new file mode 100644 index 000000000..b01a1c56e --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallMainState.cs @@ -0,0 +1,155 @@ +using RoR2; +using SS2.Components; +using UnityEngine; +using UnityEngine.Networking; + +namespace EntityStates.NemToolbot +{ + /// + /// Body main state for ball form. Replaces GenericCharacterMain while NemToolbot + /// is rolled up. Provides momentum-based movement with higher top speed, modified + /// air control, and velocity tracking for speed-based damage scaling. + /// + public class BallMainState : BaseCharacterMain + { + public static float moveSpeedMultiplier = 1.6f; + public static float ballAirControl = 0.15f; + public static float accelerationMultiplier = 0.6f; + public static float momentumDamping = 0.98f; + + public static string enterSoundString = ""; + public static string loopSoundString = ""; + public static string exitSoundString = ""; + + private NemToolbotController controller; + private float originalMoveSpeed; + private float originalAcceleration; + private float originalAirControl; + private uint loopSoundID; + + private static readonly int ballModeHash = Animator.StringToHash("BallMode"); + private static readonly int aimWeightHash = Animator.StringToHash("aimWeight"); + + public override void OnEnter() + { + base.OnEnter(); + + if (!gameObject.TryGetComponent(out controller)) + { + Debug.LogError("NemToolbot BallMainState: Failed to get NemToolbotController on " + gameObject.name); + } + + // Store original motor parameters for restoration on exit + if (characterBody != null) + { + originalMoveSpeed = characterBody.baseMoveSpeed; + originalAcceleration = characterBody.baseAcceleration; + characterBody.baseMoveSpeed *= moveSpeedMultiplier; + characterBody.baseAcceleration *= accelerationMultiplier; + characterBody.statsDirty = true; + } + + if (characterMotor != null) + { + originalAirControl = characterMotor.airControl; + characterMotor.airControl = ballAirControl; + } + + if (NetworkServer.active && characterBody != null) + { + characterBody.AddBuff(RoR2Content.Buffs.ArmorBoost); + } + + Debug.Log($"[NemToolbot] BallMainState: OnEnter (moveSpeed={characterBody?.baseMoveSpeed:F1}, accel={characterBody?.baseAcceleration:F1}, airControl={characterMotor?.airControl:F2})"); + + Util.PlaySound(enterSoundString, gameObject); + loopSoundID = Util.PlaySound(loopSoundString, gameObject); + + PlayCrossfade("Body", "BallModeEnter", 0.1f); + if (modelAnimator != null) + { + modelAnimator.SetFloat(aimWeightHash, 0f); + } + + if (modelLocator != null) + { + modelLocator.normalizeToFloor = true; + } + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + + if (isAuthority && characterBody != null) + { + characterBody.isSprinting = true; + } + + // Apply momentum damping for ball-like deceleration + if (isAuthority && characterMotor != null && characterMotor.isGrounded) + { + Vector3 velocity = characterMotor.velocity; + velocity.x *= momentumDamping; + velocity.z *= momentumDamping; + characterMotor.velocity = velocity; + } + + // Regenerate ammo for all weapons while in ball form + if (NetworkServer.active && controller != null) + { + controller.RegenAllAmmo(GetDeltaTime()); + } + } + + public override void OnExit() + { + AkSoundEngine.StopPlayingID(loopSoundID); + Util.PlaySound(exitSoundString, gameObject); + + if (characterBody != null) + { + characterBody.baseMoveSpeed = originalMoveSpeed; + characterBody.baseAcceleration = originalAcceleration; + characterBody.statsDirty = true; + characterBody.isSprinting = false; + } + + if (characterMotor != null) + { + characterMotor.airControl = originalAirControl; + } + + if (NetworkServer.active && characterBody != null) + { + characterBody.RemoveBuff(RoR2Content.Buffs.ArmorBoost); + } + + if (NetworkServer.active && controller != null) + { + controller.ResetRegenAccumulators(); + } + + if (modelAnimator != null) + { + modelAnimator.SetFloat(aimWeightHash, 1f); + } + + if (modelLocator != null) + { + modelLocator.normalizeToFloor = false; + } + + Debug.Log($"[NemToolbot] BallMainState: OnExit (restoring moveSpeed={originalMoveSpeed:F1}, accel={originalAcceleration:F1}, airControl={originalAirControl:F2})"); + + PlayAnimation("Body", "BallModeExit"); + + base.OnExit(); + } + + public override InterruptPriority GetMinimumInterruptPriority() + { + return InterruptPriority.Skill; + } + } +} diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallMainState.cs.meta b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallMainState.cs.meta new file mode 100644 index 000000000..8a359dc13 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallMainState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1da9d000512fc894bbabdd7bf5bb9dd9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallSlam.cs b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallSlam.cs new file mode 100644 index 000000000..6230656b0 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallSlam.cs @@ -0,0 +1,176 @@ +using RoR2; +using SS2.Components; +using UnityEngine; + +namespace EntityStates.NemToolbot +{ + /// + /// Ball form primary (airborne). Slams downward similar to Loader's GroundSlam / + /// Overwatch Wrecking Ball's slam. Accelerates downward, detonates a BlastAttack + /// on ground contact with speed-scaled damage. + /// + public class BallSlam : BaseCharacterMain + { + public static float airControl = 0.15f; + public static float minimumDuration = 0.15f; + public static float verticalAcceleration = -80f; + public static float exitSlowdownCoefficient = 0.3f; + public static float exitVerticalVelocity = 8f; + public static float blastRadius = 12f; + public static float blastDamageCoefficient = 5f; + public static float blastProcCoefficient = 1f; + public static float blastForce = 2000f; + public static float maxDuration = 10f; + public static Vector3 blastBonusForce = Vector3.zero; + + public static string enterSoundString = ""; + public static GameObject blastEffectPrefab; + public static GameObject blastImpactEffectPrefab; + public static GameObject slamEffectPrefab; + + private NemToolbotController controller; + private float previousAirControl; + private bool detonateNextFrame; + private GameObject slamEffectInstance; + + public override void OnEnter() + { + base.OnEnter(); + + if (!gameObject.TryGetComponent(out controller)) + { + Debug.LogError("NemToolbot BallSlam: Failed to get NemToolbotController on " + gameObject.name); + } + + PlayCrossfade("Body", "BallSlam", 0.1f); + + if (isAuthority && characterMotor != null) + { + characterMotor.onMovementHit += OnMovementHit; + } + + Util.PlaySound(enterSoundString, gameObject); + + if (characterMotor != null) + { + previousAirControl = characterMotor.airControl; + characterMotor.airControl = airControl; + } + + if (slamEffectPrefab != null) + { + Transform modelChild = FindModelChild("Root"); + if (modelChild != null) + { + slamEffectInstance = Object.Instantiate(slamEffectPrefab, modelChild); + } + } + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + + if (isAuthority && characterMotor != null) + { + // Allow limited steering during descent + characterMotor.moveDirection = inputBank.moveVector; + if (characterDirection != null) + { + characterDirection.moveVector = characterMotor.moveDirection; + } + + // Accelerate downward faster than normal gravity + characterMotor.velocity.y += verticalAcceleration * GetDeltaTime(); + + // Safety timeout if we never hit the ground + if (fixedAge >= maxDuration) + { + DetonateAuthority(); + outer.SetNextStateToMain(); + return; + } + + // Detonate when hitting the ground after minimum duration + if (fixedAge >= minimumDuration && (detonateNextFrame || characterMotor.Motor.GroundingStatus.IsStableOnGround)) + { + DetonateAuthority(); + outer.SetNextStateToMain(); + } + } + } + + public override void OnExit() + { + if (isAuthority && characterMotor != null) + { + characterMotor.onMovementHit -= OnMovementHit; + characterMotor.Motor.ForceUnground(); + characterMotor.velocity *= exitSlowdownCoefficient; + characterMotor.velocity.y = exitVerticalVelocity; + } + + if (characterMotor != null) + { + characterMotor.airControl = previousAirControl; + } + + if (slamEffectInstance != null) + { + EntityState.Destroy(slamEffectInstance); + } + + base.OnExit(); + } + + private void OnMovementHit(ref CharacterMotor.MovementHitInfo movementHitInfo) + { + detonateNextFrame = true; + } + + private BlastAttack.Result DetonateAuthority() + { + if (!isAuthority) + return default; + + Vector3 footPosition = characterBody.footPosition; + + float speedMultiplier = controller != null ? controller.GetDamageMultiplierFromSpeed() : 1f; + + if (blastEffectPrefab != null) + { + EffectManager.SpawnEffect(blastEffectPrefab, new EffectData + { + origin = footPosition, + scale = blastRadius + }, transmit: true); + } + + BlastAttack blastAttack = new BlastAttack(); + blastAttack.attacker = gameObject; + blastAttack.baseDamage = damageStat * blastDamageCoefficient * speedMultiplier; + blastAttack.baseForce = blastForce; + blastAttack.bonusForce = blastBonusForce; + blastAttack.crit = RollCrit(); + blastAttack.damageType = DamageType.Stun1s; + blastAttack.falloffModel = BlastAttack.FalloffModel.None; + blastAttack.procCoefficient = blastProcCoefficient; + blastAttack.radius = blastRadius; + blastAttack.position = footPosition; + blastAttack.attackerFiltering = AttackerFiltering.NeverHitSelf; + if (blastImpactEffectPrefab != null) + { + blastAttack.impactEffect = EffectCatalog.FindEffectIndexFromPrefab(blastImpactEffectPrefab); + } + blastAttack.teamIndex = teamComponent.teamIndex; + blastAttack.damageType.damageSource = DamageSource.Special; + + return blastAttack.Fire(); + } + + public override InterruptPriority GetMinimumInterruptPriority() + { + return InterruptPriority.Frozen; + } + } +} diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallSlam.cs.meta b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallSlam.cs.meta new file mode 100644 index 000000000..771442fa0 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/BallSlam.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 01b19ffad6eb5ee4eaf69e96d597f97c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/DeployCharges.cs b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/DeployCharges.cs new file mode 100644 index 000000000..f7213c785 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/DeployCharges.cs @@ -0,0 +1,106 @@ +using RoR2; +using RoR2.Projectile; +using UnityEngine; + +namespace EntityStates.NemToolbot +{ + /// + /// Utility skill shared between deployed and ball forms. + /// Drops 4 charge projectiles at cardinal directions around the character. + /// Each charge detonates after a delay (handled by the projectile prefab's + /// ProjectileImpactExplosion with lifetime). + /// + public class DeployCharges : BaseSkillState + { + public static float damageCoefficient = 4f; + public static float baseDuration = 0.5f; + public static float chargeSpawnRadius = 2f; + public static float force = 500f; + + public static string soundString = ""; + public static GameObject muzzleFlashPrefab; + public static GameObject chargeProjectilePrefab; + + private float duration; + + // Cardinal offsets relative to the character's forward direction + private static readonly Vector3[] cardinalOffsets = new Vector3[] + { + Vector3.forward, + Vector3.back, + Vector3.left, + Vector3.right + }; + + public override void OnEnter() + { + base.OnEnter(); + duration = baseDuration / attackSpeedStat; + + Util.PlaySound(soundString, gameObject); + if (muzzleFlashPrefab != null) + { + EffectManager.SimpleMuzzleFlash(muzzleFlashPrefab, gameObject, "Muzzle", transmit: false); + } + + PlayCrossfade("Gesture, Override", "DeployCharges", "DeployCharges.playbackRate", duration, 0.05f); + + if (isAuthority) + { + FireCharges(); + } + } + + private void FireCharges() + { + if (chargeProjectilePrefab == null) + { + Debug.LogError("NemToolbot DeployCharges: chargeProjectilePrefab is null."); + return; + } + + Vector3 footPosition = characterBody.footPosition; + Quaternion characterRotation = Quaternion.LookRotation(characterDirection != null ? characterDirection.forward : transform.forward, Vector3.up); + + DamageTypeCombo damageType = DamageType.Generic; + damageType.damageSource = DamageSource.Utility; + + for (int i = 0; i < cardinalOffsets.Length; i++) + { + Vector3 worldOffset = characterRotation * (cardinalOffsets[i] * chargeSpawnRadius); + Vector3 spawnPosition = footPosition + worldOffset; + + FireProjectileInfo fireProjectileInfo = default(FireProjectileInfo); + fireProjectileInfo.projectilePrefab = chargeProjectilePrefab; + fireProjectileInfo.position = spawnPosition; + fireProjectileInfo.rotation = Quaternion.identity; + fireProjectileInfo.owner = gameObject; + fireProjectileInfo.damage = damageStat * damageCoefficient; + fireProjectileInfo.force = force; + fireProjectileInfo.crit = RollCrit(); + fireProjectileInfo.damageTypeOverride = damageType; + + ProjectileManager.instance.FireProjectile(fireProjectileInfo); + } + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + if (isAuthority && fixedAge >= duration) + { + outer.SetNextStateToMain(); + } + } + + public override void OnExit() + { + base.OnExit(); + } + + public override InterruptPriority GetMinimumInterruptPriority() + { + return InterruptPriority.PrioritySkill; + } + } +} diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/DeployCharges.cs.meta b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/DeployCharges.cs.meta new file mode 100644 index 000000000..1d244d0ed --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/DeployCharges.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3ae1d33864b399748b15e536cbe55183 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/DispatchWeapon.cs b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/DispatchWeapon.cs new file mode 100644 index 000000000..266863481 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/DispatchWeapon.cs @@ -0,0 +1,78 @@ +using SS2.Components; +using UnityEngine; +using UnityEngine.Networking; + +namespace EntityStates.NemToolbot +{ + /// + /// Primary skill dispatcher for deployed form. + /// Reads the currently selected weapon from NemToolbotController + /// and immediately transitions to the appropriate weapon entity state. + /// + public class DispatchWeapon : BaseState + { + private NemToolbotController controller; + private NemToolbotController.WeaponType cachedWeapon = NemToolbotController.WeaponType.RapidLaser; + + public override void OnEnter() + { + base.OnEnter(); + + if (isAuthority) + { + if (!gameObject.TryGetComponent(out controller)) + { + Debug.LogError("NemToolbot DispatchWeapon: Failed to get NemToolbotController on " + gameObject.name); + outer.SetNextStateToMain(); + return; + } + + cachedWeapon = controller.currentWeapon; + + if (!controller.HasAmmo(cachedWeapon)) + { + Debug.Log($"[NemToolbot] DispatchWeapon: No ammo for {cachedWeapon}, returning to main"); + outer.SetNextStateToMain(); + return; + } + + Debug.Log($"[NemToolbot] DispatchWeapon: Firing {cachedWeapon} (ammo: {controller.GetAmmo(cachedWeapon)})"); + EntityState nextState = cachedWeapon switch + { + NemToolbotController.WeaponType.Shotgun => new FireShotgun(), + NemToolbotController.WeaponType.RapidLaser => new FireRapidLaser(), + NemToolbotController.WeaponType.GrenadeLauncher => new FireGrenadeLauncher(), + NemToolbotController.WeaponType.SniperLaser => new FireSniperLaser(), + _ => new FireRapidLaser() + }; + outer.SetNextState(nextState); + } + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + if (isAuthority) + { + outer.SetNextStateToMain(); + } + } + + public override void OnSerialize(NetworkWriter writer) + { + base.OnSerialize(writer); + writer.Write((byte)cachedWeapon); + } + + public override void OnDeserialize(NetworkReader reader) + { + base.OnDeserialize(reader); + cachedWeapon = (NemToolbotController.WeaponType)reader.ReadByte(); + } + + public override InterruptPriority GetMinimumInterruptPriority() + { + return InterruptPriority.Any; + } + } +} diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/DispatchWeapon.cs.meta b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/DispatchWeapon.cs.meta new file mode 100644 index 000000000..b04d3a3bc --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/DispatchWeapon.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f48ea39468614b04aa0d2dad625b3bbd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireGrapplingHook.cs b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireGrapplingHook.cs new file mode 100644 index 000000000..70e7d7e62 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireGrapplingHook.cs @@ -0,0 +1,171 @@ +using RoR2; +using RoR2.Projectile; +using UnityEngine; +using UnityEngine.Networking; + +namespace EntityStates.NemToolbot +{ + /// + /// Ball form secondary. Fires a grappling hook projectile that pulls NemToolbot + /// toward the impact point, following Loader's FireHook pattern. The hook projectile + /// handles the actual pull physics; this state manages animation and lifetime. + /// + /// Key difference from Loader: pressing the skill button again disconnects the hook. + /// + public class FireGrapplingHook : BaseSkillState + { + public static GameObject projectilePrefab; + + public static float damageCoefficient = 1f; + public static string fireSoundString = ""; + public static string disconnectSoundString = ""; + public static GameObject muzzleflashEffectPrefab; + public static string muzzleString = "Muzzle"; + public static float maxDuration = 8f; + + /// + /// Reference to the live hook projectile. Set by the projectile via SetHookReference(). + /// When this becomes null (projectile destroyed), the state exits. + /// + public GameObject hookInstance; + + protected ProjectileStickOnImpact hookStickOnImpact; + private bool isStuck; + private bool hadHookInstance; + private bool hasReleasedButton; + + private static readonly int FireHookIntroHash = Animator.StringToHash("FireHookIntro"); + private static readonly int FireHookLoopHash = Animator.StringToHash("FireHookLoop"); + private static readonly int FireHookExitHash = Animator.StringToHash("FireHookExit"); + + public override void OnEnter() + { + base.OnEnter(); + + if (isAuthority) + { + Ray ray = GetAimRay(); + if (projectilePrefab != null) + { + TrajectoryAimAssist.ApplyTrajectoryAimAssist(ref ray, projectilePrefab, gameObject); + + FireProjectileInfo fireProjectileInfo = default(FireProjectileInfo); + fireProjectileInfo.position = ray.origin; + fireProjectileInfo.rotation = Quaternion.LookRotation(ray.direction); + fireProjectileInfo.crit = characterBody.RollCrit(); + fireProjectileInfo.damage = damageStat * damageCoefficient; + fireProjectileInfo.force = 0f; + fireProjectileInfo.damageColorIndex = DamageColorIndex.Default; + fireProjectileInfo.damageTypeOverride = DamageTypeCombo.GenericSecondary; + fireProjectileInfo.procChainMask = default(ProcChainMask); + fireProjectileInfo.projectilePrefab = projectilePrefab; + fireProjectileInfo.owner = gameObject; + + ProjectileManager.instance.FireProjectile(fireProjectileInfo); + } + else + { + Debug.LogError("NemToolbot FireGrapplingHook: projectilePrefab is null."); + outer.SetNextStateToMain(); + return; + } + } + + if (muzzleflashEffectPrefab != null) + { + EffectManager.SimpleMuzzleFlash(muzzleflashEffectPrefab, gameObject, muzzleString, transmit: false); + } + Util.PlaySound(fireSoundString, gameObject); + + PlayAnimation("Grapple", FireHookIntroHash); + } + + /// + /// Called by the hook projectile to register itself with this state. + /// The projectile finds its owner's FireGrapplingHook state and hands back a reference. + /// + public void SetHookReference(GameObject hook) + { + hookInstance = hook; + if (hook.TryGetComponent(out ProjectileStickOnImpact stickComponent)) + { + hookStickOnImpact = stickComponent; + } + else + { + Debug.LogError("NemToolbot FireGrapplingHook: Hook projectile missing ProjectileStickOnImpact on " + hook.name); + } + hadHookInstance = true; + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + + // Transition animation when hook sticks + if (hookStickOnImpact != null) + { + if (hookStickOnImpact.stuck && !isStuck) + { + PlayAnimation("Grapple", FireHookLoopHash); + } + isStuck = hookStickOnImpact.stuck; + } + + if (!isAuthority) + return; + + // Track button release so we can detect a second press to disconnect + if (!hasReleasedButton && !IsKeyDownAuthority()) + { + hasReleasedButton = true; + } + + // Disconnect on second press (button was released then pressed again) + if (hasReleasedButton && IsKeyDownAuthority() && hookInstance != null) + { + Util.PlaySound(disconnectSoundString, gameObject); + EntityState.Destroy(hookInstance); + hookInstance = null; + } + + // Failsafe: exit if hook was never linked (e.g. missing ModelLocator/ChildLocator) + if (fixedAge >= maxDuration) + { + outer.SetNextStateToMain(); + return; + } + + // Exit when hook projectile existed and is now destroyed + if (!hookInstance && hadHookInstance) + { + outer.SetNextStateToMain(); + } + } + + public override void OnExit() + { + PlayAnimation("Grapple", FireHookExitHash); + if (muzzleflashEffectPrefab != null) + { + EffectManager.SimpleMuzzleFlash(muzzleflashEffectPrefab, gameObject, muzzleString, transmit: false); + } + + // Clean up hook if still alive when state is forcibly exited + if (hookInstance != null) + { + if (NetworkServer.active) + { + EntityState.Destroy(hookInstance); + } + } + + base.OnExit(); + } + + public override InterruptPriority GetMinimumInterruptPriority() + { + return InterruptPriority.Pain; + } + } +} diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireGrapplingHook.cs.meta b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireGrapplingHook.cs.meta new file mode 100644 index 000000000..2386c0505 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireGrapplingHook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba489bb3e86677a4788911256ead21ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireGrenadeLauncher.cs b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireGrenadeLauncher.cs new file mode 100644 index 000000000..e45fa708c --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireGrenadeLauncher.cs @@ -0,0 +1,101 @@ +using RoR2; +using RoR2.Projectile; +using SS2.Components; +using UnityEngine; +using UnityEngine.Networking; + +namespace EntityStates.NemToolbot +{ + public class FireGrenadeLauncher : BaseSkillState + { + public static float damageCoefficient = 3f; + public static float force = 1500f; + public static float baseDuration = 0.6f; + public static float recoilAmplitude = 1.5f; + public static float spreadBloomValue = 1f; + + public static string muzzleString = "Muzzle"; + public static string soundString = ""; + public static GameObject muzzleFlashPrefab; + public static GameObject projectilePrefab; + + private float duration; + + public override void OnEnter() + { + base.OnEnter(); + duration = baseDuration / attackSpeedStat; + characterBody.SetAimTimer(duration + 1f); + + if (isAuthority) + { + if (!gameObject.TryGetComponent(out NemToolbotController controller)) + { + Debug.LogError("FireGrenadeLauncher: NemToolbotController not found."); + } + else if (NetworkServer.active && !controller.TryConsumeAmmo(NemToolbotController.WeaponType.GrenadeLauncher)) + { + outer.SetNextStateToMain(); + return; + } + } + + Util.PlaySound(soundString, gameObject); + if (muzzleFlashPrefab != null) + { + EffectManager.SimpleMuzzleFlash(muzzleFlashPrefab, gameObject, muzzleString, transmit: false); + } + + PlayCrossfade("Gesture, Override", "FireGrenade", "FireGrenade.playbackRate", duration, 0.05f); + AddRecoil(-1f * recoilAmplitude, -1.5f * recoilAmplitude, -0.25f * recoilAmplitude, 0.25f * recoilAmplitude); + + if (isAuthority) + { + Ray aimRay = GetAimRay(); + + if (projectilePrefab != null) + { + DamageTypeCombo damageType = DamageType.Generic; + damageType.damageSource = DamageSource.Primary; + + FireProjectileInfo fireProjectileInfo = default(FireProjectileInfo); + fireProjectileInfo.projectilePrefab = projectilePrefab; + fireProjectileInfo.position = aimRay.origin; + fireProjectileInfo.rotation = Util.QuaternionSafeLookRotation(aimRay.direction); + fireProjectileInfo.owner = gameObject; + fireProjectileInfo.damage = damageStat * damageCoefficient; + fireProjectileInfo.force = force; + fireProjectileInfo.crit = RollCrit(); + fireProjectileInfo.damageTypeOverride = damageType; + + ProjectileManager.instance.FireProjectile(fireProjectileInfo); + } + else + { + Debug.LogError("NemToolbot FireGrenadeLauncher: projectilePrefab is null."); + } + } + + characterBody.AddSpreadBloom(spreadBloomValue); + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + if (isAuthority && fixedAge >= duration) + { + outer.SetNextStateToMain(); + } + } + + public override void OnExit() + { + base.OnExit(); + } + + public override InterruptPriority GetMinimumInterruptPriority() + { + return InterruptPriority.Skill; + } + } +} diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireGrenadeLauncher.cs.meta b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireGrenadeLauncher.cs.meta new file mode 100644 index 000000000..2ea678972 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireGrenadeLauncher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bce995f90d3664e4fa59c2420f76ec67 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireRapidLaser.cs b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireRapidLaser.cs new file mode 100644 index 000000000..2349b6810 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireRapidLaser.cs @@ -0,0 +1,130 @@ +using RoR2; +using SS2.Components; +using UnityEngine; +using UnityEngine.Networking; + +namespace EntityStates.NemToolbot +{ + public class FireRapidLaser : BaseSkillState + { + public static float damageCoefficient = 0.8f; + public static float procCoefficient = 0.7f; + public static float baseFireInterval = 0.12f; + public static float force = 100f; + public static float recoilAmplitude = 0.4f; + public static float spreadBloomValue = 0.3f; + public static float maxDistance = 60f; + public static float bulletRadius = 0.4f; + public static float baseDuration = 0.1f; + + public static string muzzleString = "Muzzle"; + public static string soundString = ""; + public static GameObject muzzleFlashPrefab; + public static GameObject tracerEffectPrefab; + public static GameObject hitEffectPrefab; + + private NemToolbotController controller; + private float fireTimer; + private float duration; + + public override void OnEnter() + { + base.OnEnter(); + duration = baseDuration / attackSpeedStat; + characterBody.SetAimTimer(2f); + + if (!gameObject.TryGetComponent(out controller)) + { + Debug.LogError("FireRapidLaser: NemToolbotController not found."); + } + + PlayCrossfade("Gesture, Override", "FireRapidLaser", "FireRapidLaser.playbackRate", duration, 0.05f); + FireBullet(); + } + + private void FireBullet() + { + fireTimer = baseFireInterval / attackSpeedStat; + + if (isAuthority && controller != null && NetworkServer.active && !controller.TryConsumeAmmo(NemToolbotController.WeaponType.RapidLaser)) + { + outer.SetNextStateToMain(); + return; + } + + Util.PlaySound(soundString, gameObject); + if (muzzleFlashPrefab != null) + { + EffectManager.SimpleMuzzleFlash(muzzleFlashPrefab, gameObject, muzzleString, transmit: false); + } + + AddRecoil(-0.5f * recoilAmplitude, -0.8f * recoilAmplitude, -0.3f * recoilAmplitude, 0.3f * recoilAmplitude); + + if (isAuthority) + { + Ray aimRay = GetAimRay(); + DamageTypeCombo damageType = DamageType.Generic; + damageType.damageSource = DamageSource.Primary; + + BulletAttack bulletAttack = new BulletAttack + { + bulletCount = 1, + aimVector = aimRay.direction, + origin = aimRay.origin, + damage = damageCoefficient * damageStat, + damageColorIndex = DamageColorIndex.Default, + damageType = damageType, + falloffModel = BulletAttack.FalloffModel.DefaultBullet, + maxDistance = maxDistance, + force = force, + hitMask = LayerIndex.CommonMasks.bullet, + minSpread = 0f, + maxSpread = characterBody.spreadBloomAngle, + isCrit = RollCrit(), + owner = gameObject, + muzzleName = muzzleString, + smartCollision = true, + procChainMask = default(ProcChainMask), + procCoefficient = procCoefficient, + radius = bulletRadius, + sniper = false, + stopperMask = LayerIndex.CommonMasks.bullet, + weapon = gameObject, + tracerEffectPrefab = tracerEffectPrefab, + spreadPitchScale = 1f, + spreadYawScale = 1f, + hitEffectPrefab = hitEffectPrefab + }; + bulletAttack.Fire(); + } + + characterBody.AddSpreadBloom(spreadBloomValue); + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + + fireTimer -= GetDeltaTime(); + if (fireTimer <= 0f) + { + FireBullet(); + } + + if (isAuthority && !IsKeyDownAuthority()) + { + outer.SetNextStateToMain(); + } + } + + public override void OnExit() + { + base.OnExit(); + } + + public override InterruptPriority GetMinimumInterruptPriority() + { + return InterruptPriority.Skill; + } + } +} diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireRapidLaser.cs.meta b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireRapidLaser.cs.meta new file mode 100644 index 000000000..542a440b1 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireRapidLaser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f59aef8b8afae8a40a0d11288538ecd3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireShotgun.cs b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireShotgun.cs new file mode 100644 index 000000000..4ab1edd70 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireShotgun.cs @@ -0,0 +1,117 @@ +using RoR2; +using SS2.Components; +using UnityEngine; +using UnityEngine.Networking; + +namespace EntityStates.NemToolbot +{ + public class FireShotgun : BaseSkillState + { + public static float damageCoefficient = 1.2f; + public static float procCoefficient = 0.5f; + public static float baseDuration = 0.6f; + public static float force = 300f; + public static float recoilAmplitude = 3f; + public static float spreadBloomValue = 1.5f; + public static int pelletCount = 6; + public static float spreadAngle = 10f; + public static float maxDistance = 12f; + public static float bulletRadius = 0.3f; + + public static string muzzleString = "Muzzle"; + public static string soundString = ""; + public static GameObject muzzleFlashPrefab; + public static GameObject tracerEffectPrefab; + public static GameObject hitEffectPrefab; + + private float duration; + + public override void OnEnter() + { + base.OnEnter(); + duration = baseDuration / attackSpeedStat; + characterBody.SetAimTimer(duration + 1f); + + if (isAuthority) + { + if (!gameObject.TryGetComponent(out NemToolbotController controller)) + { + Debug.LogError("FireShotgun: NemToolbotController not found."); + } + else if (NetworkServer.active && !controller.TryConsumeAmmo(NemToolbotController.WeaponType.Shotgun)) + { + outer.SetNextStateToMain(); + return; + } + } + + Util.PlaySound(soundString, gameObject); + if (muzzleFlashPrefab != null) + { + EffectManager.SimpleMuzzleFlash(muzzleFlashPrefab, gameObject, muzzleString, transmit: false); + } + + PlayCrossfade("Gesture, Override", "FireShotgun", "FireShotgun.playbackRate", duration, 0.05f); + AddRecoil(-2f * recoilAmplitude, -3f * recoilAmplitude, -0.5f * recoilAmplitude, 0.5f * recoilAmplitude); + + if (isAuthority) + { + Ray aimRay = GetAimRay(); + DamageTypeCombo damageType = DamageType.Generic; + damageType.damageSource = DamageSource.Primary; + + BulletAttack bulletAttack = new BulletAttack + { + bulletCount = (uint)pelletCount, + aimVector = aimRay.direction, + origin = aimRay.origin, + damage = damageCoefficient * damageStat, + damageColorIndex = DamageColorIndex.Default, + damageType = damageType, + falloffModel = BulletAttack.FalloffModel.Buckshot, + maxDistance = maxDistance, + force = force, + hitMask = LayerIndex.CommonMasks.bullet, + minSpread = 0f, + maxSpread = spreadAngle, + isCrit = RollCrit(), + owner = gameObject, + muzzleName = muzzleString, + smartCollision = true, + procChainMask = default(ProcChainMask), + procCoefficient = procCoefficient, + radius = bulletRadius, + sniper = false, + stopperMask = LayerIndex.CommonMasks.bullet, + weapon = gameObject, + tracerEffectPrefab = tracerEffectPrefab, + spreadPitchScale = 1f, + spreadYawScale = 1f, + hitEffectPrefab = hitEffectPrefab + }; + bulletAttack.Fire(); + } + + characterBody.AddSpreadBloom(spreadBloomValue); + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + if (isAuthority && fixedAge >= duration) + { + outer.SetNextStateToMain(); + } + } + + public override void OnExit() + { + base.OnExit(); + } + + public override InterruptPriority GetMinimumInterruptPriority() + { + return InterruptPriority.Skill; + } + } +} diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireShotgun.cs.meta b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireShotgun.cs.meta new file mode 100644 index 000000000..e6ca7b3a6 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireShotgun.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 17b0c7bc00382784fbd9b8a56970264a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireSniperLaser.cs b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireSniperLaser.cs new file mode 100644 index 000000000..336c99740 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireSniperLaser.cs @@ -0,0 +1,115 @@ +using RoR2; +using SS2.Components; +using UnityEngine; +using UnityEngine.Networking; + +namespace EntityStates.NemToolbot +{ + public class FireSniperLaser : BaseSkillState + { + public static float damageCoefficient = 6f; + public static float procCoefficient = 1f; + public static float baseDuration = 0.8f; + public static float force = 2500f; + public static float recoilAmplitude = 4f; + public static float spreadBloomValue = 2f; + public static float maxDistance = 1000f; + public static float bulletRadius = 0.5f; + + public static string muzzleString = "Muzzle"; + public static string soundString = ""; + public static GameObject muzzleFlashPrefab; + public static GameObject tracerEffectPrefab; + public static GameObject hitEffectPrefab; + + private float duration; + + public override void OnEnter() + { + base.OnEnter(); + duration = baseDuration / attackSpeedStat; + characterBody.SetAimTimer(duration + 2f); + + if (isAuthority) + { + if (!gameObject.TryGetComponent(out NemToolbotController controller)) + { + Debug.LogError("FireSniperLaser: NemToolbotController not found."); + } + else if (NetworkServer.active && !controller.TryConsumeAmmo(NemToolbotController.WeaponType.SniperLaser)) + { + outer.SetNextStateToMain(); + return; + } + } + + Util.PlaySound(soundString, gameObject); + if (muzzleFlashPrefab != null) + { + EffectManager.SimpleMuzzleFlash(muzzleFlashPrefab, gameObject, muzzleString, transmit: false); + } + + PlayCrossfade("Gesture, Override", "FireSniper", "FireSniper.playbackRate", duration, 0.05f); + AddRecoil(-2f * recoilAmplitude, -3f * recoilAmplitude, -0.5f * recoilAmplitude, 0.5f * recoilAmplitude); + + if (isAuthority) + { + Ray aimRay = GetAimRay(); + DamageTypeCombo damageType = DamageType.Generic; + damageType.damageSource = DamageSource.Primary; + + BulletAttack bulletAttack = new BulletAttack + { + bulletCount = 1, + aimVector = aimRay.direction, + origin = aimRay.origin, + damage = damageCoefficient * damageStat, + damageColorIndex = DamageColorIndex.Default, + damageType = damageType, + falloffModel = BulletAttack.FalloffModel.None, + maxDistance = maxDistance, + force = force, + hitMask = LayerIndex.CommonMasks.bullet, + minSpread = 0f, + maxSpread = 0f, + isCrit = RollCrit(), + owner = gameObject, + muzzleName = muzzleString, + smartCollision = true, + procChainMask = default(ProcChainMask), + procCoefficient = procCoefficient, + radius = bulletRadius, + sniper = true, + stopperMask = LayerIndex.world.mask, + weapon = gameObject, + tracerEffectPrefab = tracerEffectPrefab, + spreadPitchScale = 0f, + spreadYawScale = 0f, + hitEffectPrefab = hitEffectPrefab + }; + bulletAttack.Fire(); + } + + characterBody.AddSpreadBloom(spreadBloomValue); + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + if (isAuthority && fixedAge >= duration) + { + outer.SetNextStateToMain(); + } + } + + public override void OnExit() + { + base.OnExit(); + } + + public override InterruptPriority GetMinimumInterruptPriority() + { + return InterruptPriority.PrioritySkill; + } + } +} diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireSniperLaser.cs.meta b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireSniperLaser.cs.meta new file mode 100644 index 000000000..7d70d2b30 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/FireSniperLaser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d02824ba7a23f504189609f26705a297 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/RangeFinder.cs b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/RangeFinder.cs new file mode 100644 index 000000000..d2bfcf818 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/RangeFinder.cs @@ -0,0 +1,130 @@ +using RoR2; +using SS2.Components; +using UnityEngine; +using UnityEngine.AddressableAssets; +using UnityEngine.Networking; + +namespace EntityStates.NemToolbot +{ + /// + /// Secondary skill for deployed form. + /// Hold to project a laser from the muzzle. The laser raycasts and shows the + /// range to the first surface/enemy hit, along with which weapon that range + /// would select. On release, sets the weapon on NemToolbotController. + /// + public class RangeFinder : BaseSkillState + { + public static float maxRange = 200f; + public static string muzzleString = "Muzzle"; + public static string enterSoundString = ""; + public static string exitSoundString = ""; + + private static Material laserMaterial; + + private NemToolbotController controller; + private GameObject laserObject; + private LineRenderer lineRenderer; + private Transform muzzleTransform; + private float hitDistance; + private NemToolbotController.WeaponType pendingWeapon; + private Vector3 lastLaserOrigin; + private Vector3 lastLaserEndPoint; + + public override void OnEnter() + { + base.OnEnter(); + + if (!gameObject.TryGetComponent(out controller)) + { + Debug.LogError("NemToolbot RangeFinder: Failed to get NemToolbotController on " + gameObject.name); + } + + Util.PlaySound(enterSoundString, gameObject); + characterBody.SetAimTimer(3f); + + muzzleTransform = FindModelChild(muzzleString); + + if (laserMaterial == null) + { + laserMaterial = Addressables.LoadAssetAsync("RoR2/Base/Common/VFX/matTracerBright.mat").WaitForCompletion(); + } + + laserObject = new GameObject("NemToolbotRangeFinderLaser"); + lineRenderer = laserObject.AddComponent(); + lineRenderer.positionCount = 2; + lineRenderer.startWidth = 0.05f; + lineRenderer.endWidth = 0.05f; + lineRenderer.material = laserMaterial; + lineRenderer.startColor = Color.red; + lineRenderer.endColor = Color.red; + lineRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; + lineRenderer.receiveShadows = false; + lineRenderer.textureMode = LineTextureMode.Tile; + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + + Ray aimRay = GetAimRay(); + Vector3 endPoint; + + if (Physics.Raycast(aimRay, out RaycastHit hit, maxRange, LayerIndex.CommonMasks.bullet, QueryTriggerInteraction.Ignore)) + { + hitDistance = hit.distance; + endPoint = hit.point; + } + else + { + hitDistance = maxRange; + endPoint = aimRay.GetPoint(maxRange); + } + + pendingWeapon = NemToolbotController.GetWeaponFromRange(hitDistance); + + lastLaserOrigin = aimRay.origin; + lastLaserEndPoint = endPoint; + + if (isAuthority && !IsKeyDownAuthority()) + { + Debug.Log($"[NemToolbot] RangeFinder: Released at {hitDistance:F1}m -> selecting {pendingWeapon}"); + if (NetworkServer.active && controller != null) + { + controller.SetWeapon(pendingWeapon); + } + outer.SetNextStateToMain(); + } + } + + public override void Update() + { + base.Update(); + UpdateLaserVisuals(lastLaserOrigin, lastLaserEndPoint); + } + + private void UpdateLaserVisuals(Vector3 origin, Vector3 endPoint) + { + if (lineRenderer != null) + { + Vector3 start = muzzleTransform != null ? muzzleTransform.position : origin; + lineRenderer.SetPosition(0, start); + lineRenderer.SetPosition(1, endPoint); + } + } + + public override void OnExit() + { + Util.PlaySound(exitSoundString, gameObject); + if (laserObject != null) + { + EntityState.Destroy(laserObject); + } + base.OnExit(); + } + + public override InterruptPriority GetMinimumInterruptPriority() + { + return InterruptPriority.PrioritySkill; + } + } +} diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/RangeFinder.cs.meta b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/RangeFinder.cs.meta new file mode 100644 index 000000000..80e5d6de0 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/RangeFinder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9a51897a3bd884c45bfef777150fcb53 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/StanceSwapToBall.cs b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/StanceSwapToBall.cs new file mode 100644 index 000000000..20b9353ab --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/StanceSwapToBall.cs @@ -0,0 +1,73 @@ +using RoR2; +using SS2.Components; +using UnityEngine; +using UnityEngine.Networking; + +namespace EntityStates.NemToolbot +{ + /// + /// Deployed form special. Plays a transition animation then sets the body state + /// to BallMainState. Follows the ToolbotStanceSwap timing pattern. + /// + public class StanceSwapToBall : BaseState + { + [SerializeField] + public float baseDuration = 0.5f; + + public static string enterSoundString = ""; + + private float duration; + private NemToolbotController controller; + + public override void OnEnter() + { + base.OnEnter(); + duration = baseDuration / attackSpeedStat; + + if (!gameObject.TryGetComponent(out controller)) + { + Debug.LogError("NemToolbot StanceSwapToBall: Failed to get NemToolbotController on " + gameObject.name); + } + + Debug.Log($"[NemToolbot] StanceSwapToBall: Entering ball form (duration={duration:F2}s)"); + if (NetworkServer.active && controller != null) + { + controller.SetBallForm(true); + } + + Util.PlaySound(enterSoundString, gameObject); + PlayCrossfade("Body", "TransformToBall", "StanceSwap.playbackRate", duration, 0.1f); + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + if (isAuthority && fixedAge >= duration) + { + outer.SetNextState(new BallMainState()); + } + } + + public override void OnExit() + { + base.OnExit(); + } + + public override void OnSerialize(NetworkWriter writer) + { + base.OnSerialize(writer); + writer.Write(fixedAge); + } + + public override void OnDeserialize(NetworkReader reader) + { + base.OnDeserialize(reader); + fixedAge = reader.ReadSingle(); + } + + public override InterruptPriority GetMinimumInterruptPriority() + { + return InterruptPriority.Frozen; + } + } +} diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/StanceSwapToBall.cs.meta b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/StanceSwapToBall.cs.meta new file mode 100644 index 000000000..682875488 --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/StanceSwapToBall.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a19ad93d926de2b46bac1330060ebc1d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/StanceSwapToDeployed.cs b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/StanceSwapToDeployed.cs new file mode 100644 index 000000000..f3668dffc --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/StanceSwapToDeployed.cs @@ -0,0 +1,74 @@ +using RoR2; +using SS2.Components; +using UnityEngine; +using UnityEngine.Networking; + +namespace EntityStates.NemToolbot +{ + /// + /// Ball form special. Plays a transition animation then returns the body state + /// to main (GenericCharacterMain / deployed main state). Follows the ToolbotStanceSwap + /// timing pattern. + /// + public class StanceSwapToDeployed : BaseState + { + [SerializeField] + public float baseDuration = 0.5f; + + public static string enterSoundString = ""; + + private float duration; + private NemToolbotController controller; + + public override void OnEnter() + { + base.OnEnter(); + duration = baseDuration / attackSpeedStat; + + if (!gameObject.TryGetComponent(out controller)) + { + Debug.LogError("NemToolbot StanceSwapToDeployed: Failed to get NemToolbotController on " + gameObject.name); + } + + Debug.Log($"[NemToolbot] StanceSwapToDeployed: Exiting ball form (duration={duration:F2}s)"); + if (NetworkServer.active && controller != null) + { + controller.SetBallForm(false); + } + + Util.PlaySound(enterSoundString, gameObject); + PlayCrossfade("Body", "TransformToDeployed", "StanceSwap.playbackRate", duration, 0.1f); + } + + public override void FixedUpdate() + { + base.FixedUpdate(); + if (isAuthority && fixedAge >= duration) + { + outer.SetNextStateToMain(); + } + } + + public override void OnExit() + { + base.OnExit(); + } + + public override void OnSerialize(NetworkWriter writer) + { + base.OnSerialize(writer); + writer.Write(fixedAge); + } + + public override void OnDeserialize(NetworkReader reader) + { + base.OnDeserialize(reader); + fixedAge = reader.ReadSingle(); + } + + public override InterruptPriority GetMinimumInterruptPriority() + { + return InterruptPriority.Frozen; + } + } +} diff --git a/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/StanceSwapToDeployed.cs.meta b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/StanceSwapToDeployed.cs.meta new file mode 100644 index 000000000..82f0a8dfc --- /dev/null +++ b/SS2-Project/Assets/Starstorm2/Modules/EntityStates/NemToolbot/StanceSwapToDeployed.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9dedcb401bb1acb43911cd6d8359937d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/SS2-Project/Assets/ThunderKitSettings/ThunderKitSettings.asset b/SS2-Project/Assets/ThunderKitSettings/ThunderKitSettings.asset index 09659ba2e..96f677294 100644 --- a/SS2-Project/Assets/ThunderKitSettings/ThunderKitSettings.asset +++ b/SS2-Project/Assets/ThunderKitSettings/ThunderKitSettings.asset @@ -12,7 +12,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: f9057f7f601990a4883b2cb295c4f43e, type: 3} m_Name: ThunderKitSettings m_EditorClassIdentifier: - FirstLoad: 0 + FirstLoad: 1 ShowOnStartup: 1 GameExecutable: GamePath: