class Ship : AbstractRole { @CostumeAttribute var rotationSpeed = 3.0 // Acceleration when thrusting @CostumeAttribute var acceleration = 0.3 // The time between two bullets. @CostumeAttribute var firePeriod = 1.0 @CostumeAttribute var weight = 10.0 // Distance between the ship and its cargo var pickupDistance = 150 // Set later to HiggsAnomaly.instance.rodLength // Acceleration when not thrusting var drag = 0.0 // Set later to HiggsAnomaly.instance.drag val velocity = Vector2(0,0) var rotateL : Input var rotateR : Input var thrust : Input var pickUp : Input var release : Input var fire : Input // Do not move the ship, until thrust or rotate are pressed. var started = false static val IDLE = 0 // No rod static val PICKING_UP = 1 // Rod is being extended, but hasn't reached the Cargo static val ATTACHED = 2 // Rod has reached the cargo, but there is "slack" in the rod static val CARRYING = 3 // Rod is now fixed length var rodState = IDLE // IDLE, PICKING_UP, ATTACHED or CARRYING var rod : Rod = null // null when rodState == IDLE. Non-null otherwise. var rodLength = 0.0 var cargo : Cargo = null // null when rodState == IDEL. Non-null otherwise. // The time in seconds when we can next fire. var fireCooldown : double = 0 override fun begin() { actor.direction.set( Play.instance.shipGravity ) actor.direction.degrees += 180 // Ship points downwards Play.instance.ship = this val inputs = Game.instance.resources.inputs rotateL = inputs.find("rotateL") rotateR = inputs.find("rotateR") thrust = inputs.find("thrust") pickUp = inputs.find("pickUp") release = inputs.find("release") fire = inputs.find("fire") pickupDistance = Options.instance.rodLength drag = Options.instance.drag } var isThrusting = false var isRotating = false override fun tick() { if (!started) { velocity.set(0,0) } if (rotateR.isPressed()) { actor.event("rotate") isRotating = true started = true actor.direction.degrees -= rotationSpeed } else if (rotateL.isPressed()) { actor.event("rotate") isRotating = true started = true actor.direction.degrees += rotationSpeed } else { if (isRotating) { isRotating = false actor.stopEvent( "rotate" ) } } if (thrust.isPressed()) { actor.event("thrust") isThrusting = true started = true velocity += actor.direction.vector() * acceleration val puffCostume = actor.costume.chooseCostume("puff") for ( i in 0..3 ) { val child = actor.createChild( puffCostume ) (child.role as Puff).begin( this ) } } else { if (isThrusting) { isThrusting = false actor.stopEvent("thrust") } } if (pickUp.isPressed()) { if ( rodState == IDLE ) { // Look for a nearby piece of Cargo cargo = Play.instance.findNearbyCargo( actor, pickupDistance ) if ( cargo != null ) { actor.event( "pickingUp" ) val rodCostume = actor.costume.chooseCostume( "rod" ) val rodActor = actor.createChild( rodCostume ) rod = rodActor.role as Rod rodLength = 50.0 rodState = PICKING_UP } } } if (release.isPressed()) { if (rodState == CARRYING) { disconnectRod() } } if (fire.isPressed()) { if (Game.instance.seconds > fireCooldown) { actor.event( "fire" ) fireCooldown = Game.instance.seconds + firePeriod val bulletA = actor.createChild( "bullet" ) bulletA.stage = Play.instance.rocksStage bulletA.direction.set( actor.direction ) bulletA.moveForwards( actor.appearance.height()/2 + bulletA.appearance.height() + 10 ) val bullet = bulletA.role as Bullet bullet.velocity.set( velocity ) bullet.velocity += bulletA.direction.vector() * bullet.speed } } // Movement val play = Play.instance if (started) { velocity *= (1-drag) velocity += Play.instance.shipGravity actor.position += velocity } if (rodState != IDLE) { // Rotate the rod rod.actor.position.set( actor.position ) val dx = cargo.actor.x - actor.x val dy = cargo.actor.y - actor.y rod.actor.direction.set( dx, dy ) val dist = actor.position.distance( cargo.actor.position ) if ( rodState == PICKING_UP ) { if (dist > pickupDistance) { // The ship has moved too far away. actor.event( "pickupFailed" ) // Sound event later rodState = IDLE rod.actor.die() rod = null cargo = null } else { if ( pickUp.isPressed() ) { rodLength += 2 } else { rodLength -= 4 if ( rodLength < 0 ) { disconnectRod() return } } rod.actor.scale.x = rodLength / rod.actor.appearance.width() if (rodLength >= dist) { // The rod has reached the cargo. The player should now move away again, // to pick up the cargo rodState = ATTACHED actor.event( "attached" ) } } } if ( rodState == ATTACHED ) { rod.actor.scale.x = dist / rod.actor.appearance.width() if (dist >= pickupDistance) { // The rod is at the correct length for the cargo to be lifted off the ground. rodState = CARRYING cargo.pickedUp() } } if ( rodState == CARRYING ) { // The ship and the Cargo are now attached together by a rod, with gravity acting on both. // This is the fun part of the game, because the ship is tricky to control now ;-) val stretch = dist - pickupDistance val stretchX = rod.actor.direction.cos() * stretch val stretchY = rod.actor.direction.sin() * stretch val cargoFactor = weight / (cargo.weight + weight) val shipFactor = cargo.weight / (cargo.weight + weight) val angleVector = rod.actor.direction.vector() actor.position.x += stretchX * shipFactor actor.position.y += stretchY * shipFactor velocity.x += stretchX * shipFactor velocity.y += stretchY * shipFactor cargo.actor.position.x += -stretchX * cargoFactor cargo.actor.position.y += -stretchY * cargoFactor cargo.velocity.x -= stretchX * cargoFactor cargo.velocity.y -= stretchY * cargoFactor cargo.velocity += Play.instance.cargoGravity cargo.actor.position += cargo.velocity if (cargo.deadlyCheck()) { disconnectRod() } } } // Center the view on the ship. play.scrollTowards(actor.position) val deadly = play.overlappingSomethingDeadly( actor ) if (deadly != null) { disconnectRod() if ( deadly.role is ExitGate ) { val exitGate = deadly.role as ExitGate if (exitGate.isOpen) { exitGate.transportShip( this ) } else { explode() } } else { explode() } } } fun disconnectRod() { if ( rod != null ) { rod.actor.die() } if (rodState == CARRYING) { cargo.letGo() } rod = null cargo = null rodState = IDLE } fun explode() { actor.stopEvent("thrust") actor.stopEvent("rotate") if (rodState == CARRYING ) { cargo.letGo() rod.actor.die() rod = null cargo = null rodState = IDLE } Play.instance.explode( actor ) Play.instance.shipDestroyed() } }