package hedgehogform.machines
// import net.risingworld.api.assets.ModelAsset
import net.risingworld.api.Plugin
import net.risingworld.api.assets.AssetBundle
import net.risingworld.api.assets.MaterialAsset
import net.risingworld.api.assets.PrefabAsset
import net.risingworld.api.assets.TextureAsset
import net.risingworld.api.events.EventMethod
import net.risingworld.api.events.Listener
import net.risingworld.api.events.player.PlayerSpawnEvent
import net.risingworld.api.objects.Player
import net.risingworld.api.utils.Layer
import net.risingworld.api.utils.Vector3f
import net.risingworld.api.worldelements.Prefab
// import net.risingworld.api.worldelements.Model
fun formatPrint(message: String) {
println("[${ModInfo.name}] $message")
open class PrefabManager {
private var assetBundle: AssetBundle? = null
private val prefabAssets = mutableMapOf<String, PrefabAsset>()
private var bakeMaterial: MaterialAsset? = null
private var bakeTexture: TextureAsset? = null
private val spawnedPrefabs = mutableMapOf<Player, MutableList<Prefab>>()
var isInitialized: Boolean = false
fun initialize(bundlePath: String, prefabNames: List<String>) {
assetBundle = AssetBundle.loadFromFile(bundlePath)
if (assetBundle == null) {
formatPrint("[FAIL] Failed to load asset bundle from $bundlePath")
val allAssets = assetBundle!!.allAssetNames
formatPrint("[OK] Asset bundle loaded with assets: ${allAssets.toList()}")
// Print each asset for debugging
allAssets.forEach { asset ->
formatPrint("[DEBUG] Found asset: $asset")
// Load the BakeTexture texture from the bundle
val texturePath = allAssets.firstOrNull { it.contains("BakeTexture", ignoreCase = true) }
if (texturePath != null) {
formatPrint("[DEBUG] Loading texture: BakeTexture from $texturePath")
bakeTexture = TextureAsset.loadFromAssetBundle(assetBundle!!, texturePath)
formatPrint("[OK] Loaded BakeTexture texture")
// Create a material and assign the texture to it
bakeMaterial = MaterialAsset.create("BakeTextureMaterial")
bakeMaterial!!.setTexture(bakeTexture)
formatPrint("[OK] Created material and assigned BakeTexture")
formatPrint("[WARN] Failed to load BakeTexture: ${e.message}")
formatPrint("[WARN] BakeTexture not found in bundle")
// Load all prefab assets immediately
prefabNames.forEach { prefabName ->
val assetPath = allAssets.firstOrNull { it.contains(prefabName, ignoreCase = false) }
formatPrint("[DEBUG] Loading prefab asset: $prefabName from $assetPath")
val prefabAsset = PrefabAsset.loadFromAssetBundle(assetBundle!!, assetPath)
formatPrint("[DEBUG] Successfully loaded prefab asset: $prefabName")
prefabAssets[prefabName] = prefabAsset
formatPrint("[WARN] Exception while loading prefab: ${e.message}")
formatPrint("[OK] Loaded prefab asset: $prefabName from $assetPath")
formatPrint("[WARN] Prefab \"$prefabName\" not found in bundle")
fun spawnPrefab(prefabName: String, player: Player) {
formatPrint("[WARN] PrefabManager not initialized, cannot spawn \"$prefabName\"")
val prefabAsset = prefabAssets[prefabName]
if (prefabAsset == null) {
formatPrint("[FAIL] Prefab \"$prefabName\" not found in cache")
// Calculate spawn position in front of the player at eye level
val forward = player.viewDirection.normalize()
val spawnPosition = player.position.add(forward.x * 3f, 1.5f, forward.z * 3f)
formatPrint("[DEBUG] Spawning at position: $spawnPosition")
prefab.setPrefab(prefabAsset)
formatPrint("[DEBUG] Created prefab instance and assigned asset")
// Add to player to "instantiate" it into the game world
formatPrint("[DEBUG] Adding prefab to player game object...")
player.addGameObject(prefab)
// Now configure the instantiated prefab
val scale = Vector3f(1f, 1f, 1f)
prefab.setLocalScale("", scale)
formatPrint("[DEBUG] Set scale to: $scale")
prefab.setLayer(Layer.OBJECT)
formatPrint("[DEBUG] Set layer to OBJECT")
prefab.setLocalPosition(null, spawnPosition)
formatPrint("[DEBUG] Set position to: $spawnPosition")
// Apply the BakeTexture material
if (bakeMaterial != null) {
prefab.setMaterial("", bakeMaterial)
formatPrint("[DEBUG] Applied BakeTexture material to prefab")
formatPrint("[WARN] Could not apply BakeTexture material: ${e.message}")
formatPrint("[WARN] BakeTexture material not available")
// Enable physics: set isKinematic to false and useGravity to true on Rigidbody
// Note: isKinematic = false allows physics forces, collisions, and player interaction
prefab.setComponentProperty("", "Rigidbody", "isKinematic", false)
formatPrint("[DEBUG] Set Rigidbody isKinematic to false (physics enabled)")
prefab.setComponentProperty("", "Rigidbody", "useGravity", true)
formatPrint("[DEBUG] Set Rigidbody useGravity to true")
// Set mass to make it easier to push (lower mass = easier to move)
prefab.setComponentProperty("", "Rigidbody", "mass", 10.0f)
formatPrint("[DEBUG] Set Rigidbody mass to 10.0")
// Set drag to prevent sliding too much
prefab.setComponentProperty("", "Rigidbody", "drag", 0.5f)
formatPrint("[DEBUG] Set Rigidbody drag to 0.5")
// Set angular drag to prevent excessive spinning
prefab.setComponentProperty("", "Rigidbody", "angularDrag", 0.5f)
formatPrint("[DEBUG] Set Rigidbody angularDrag to 0.5")
formatPrint("[WARN] Could not set Rigidbody properties: ${e.message}")
prefab.setActive("", true)
formatPrint("[DEBUG] Set prefab active")
val playerPrefabs = spawnedPrefabs.getOrPut(player) { mutableListOf() }
playerPrefabs.add(prefab)
formatPrint("[OK] Spawned \"$prefabName\" at ${spawnPosition}")
fun playAnimation(prefab: Prefab, animationName: String) {
// Use setAnimatorTrigger to trigger an animation
prefab.setAnimatorTrigger("", animationName)
formatPrint("[INFO] Triggered animation: $animationName")
fun setAnimatorParameter(prefab: Prefab, parameter: String, value: Any) {
is Float -> prefab.setAnimatorParameter("", parameter, value)
is Int -> prefab.setAnimatorParameter("", parameter, value)
is Boolean -> prefab.setAnimatorParameter("", parameter, value)
"[WARN] Unsupported animator parameter type: ${value::class.simpleName}"
formatPrint("[INFO] Set animator parameter '$parameter' to $value")
fun startAnimation(prefab: Prefab) {
prefab.startAnimatorPlayback("")
formatPrint("[INFO] Started animator playback")
fun stopAnimation(prefab: Prefab) {
prefab.stopAnimatorPlayback("")
formatPrint("[INFO] Stopped animator playback")
formatPrint("[STOP] Cleaning up spawned prefabs...")
spawnedPrefabs.forEach { (player, prefabs) ->
prefabs.forEach { prefab ->
runCatching { player.removeGameObject(prefab) }.onFailure {
formatPrint("[WARN] Could not remove prefab: ${it.message}")
// assetBundle?.dispose() // Don't call: will cause crash
formatPrint("[STOP] Cleanup complete.")
open class MachinesPlugin : Plugin(), Listener {
private val prefabManager = PrefabManager()
override fun onEnable() {
formatPrint("[START] ${ModInfo.name} v${ModInfo.version} is starting up.")
// Load box prefab from AssetBundle with BakeTexture material
val bundlePath = "${getPath()}/assets/box.bundle"
val prefabNames = listOf("boxprefab")
prefabManager.initialize(bundlePath, prefabNames)
registerEventListener(MyListener(prefabManager))
formatPrint("[OK] Plugin successfully loaded!")
override fun onDisable() {
formatPrint("[STOP] Plugin disabled")
class MyListener(private val prefabManager: PrefabManager) : Listener {
fun onPlayerSpawn(event: PlayerSpawnEvent) {
val player = event.player ?: return
prefabManager.spawnPrefab("boxprefab", player)