package Screens { import flash.display.*; import flash.events.*; import flash.geom.Point; import flash.text.*; import Gameplay.*; import Services.*; import gs.TweenLite; /** * Gathers user's input and directs it to the level. SingleScreen is responsible for: * - acting as a controller for the HUD * - being a high level controller for LevelService * - Directing keyboard and mouse input to server * - Accepting keyboard and mouse message from server * - Moving the player(s) according to keyboard/mouse input * - Firing bullets according to keyboard/mouse input * - Detecting when bullets for this player collide with a zombie (detecting hits) */ public class SingleScreen extends Sprite { // Display widgets protected var widget : w_game = new w_game(); // Top Right - shows scores/health/ammo protected var stats : w_stats = new w_stats(); // Middle - shows stats at the end of level // Status variables for this client protected var levelComplete : Boolean; // Level is finished - do not allow player(s) to move/fire private var paused : Boolean; // Level is paused - ignore enter frame loop protected var keys:Array; // Used to store the state of keys on the keyboard // Multiplayer Variables protected var gun:Weapon; // Currently selected weapon (either user.weaponPrimary or user.weaponSecondary) protected var bullets : Array; // Array of Bullets that are on the screen protected var shot_counter:Number; // Frames until next shot protected var mouse : Point; // Location of mouse //Services protected var level : LevelService = SystemController.getInstance().levelService; protected var user : UserService = SystemController.getInstance().userService; protected var sound : SoundService = SystemController.getInstance().soundService; protected var music : MusicService = SystemController.getInstance().musicService; protected var client : ClientService = SystemController.getInstance().clientService; /** * Initializes the SingleScreen */ public function SingleScreen() { addEventListener(Event.ADDED_TO_STAGE, init); } /** * Run every time the SingleScreen is added to the stage * @param e - Event.ADDED_TO_STAGE */ protected function init(e:Event):void { // Reset Variables levelComplete = false; paused = false; level = level; shot_counter = 0; keys = new Array(); bullets = new Array(); keys['a'] = false; keys['s'] = false; keys['d'] = false; keys['w'] = false; keys['f'] = false; keys['m'] = false; if (gun != null) { widget.removeChild(gun.graphic); gun = null; } user.velocity = new Point(); user.health = 100; while (numChildren > 0) removeChildAt(0); // Start music music.play(level.levelIndex); //sound.addUser(user.name, user.weaponPrimary.type, user.index); //broadcast sound channel SystemController.getInstance().clientService.socketSend("Ssound:" + user.name + "|" + user.weaponPrimary.type + "|" + user.index); // Add background map/zombies/objects addChild(level.widget); // Add gun switchWeapons(); // Add cash/weapon/health display widget.x = stage.stageWidth; widget.y = 0; widget.zombies.y = stage.stageHeight - widget.zombies.height; addChild(widget); // Connect keyboard to controls stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown); stage.addEventListener(KeyboardEvent.KEY_UP, keyUp); addEventListener(MouseEvent.MOUSE_DOWN, mouseDown); addEventListener(MouseEvent.MOUSE_UP, mouseUp); addEventListener(MouseEvent.MOUSE_MOVE, mouseMove); mouseMove(null); stage.focus = stage; // Start game loop addEventListener(Event.ENTER_FRAME, enterFrame); } /** * Remote handler that accepts keyboard input changes * @param msg (String) a "|" separated list of arguments. Ex: index|velx|vely|posx|posy|firing */ public function inputChanged(msg : String):void { var args : Array = msg.split("|"); var index : int = parseInt(String(args[0])); //Update player velocity if (String(args[1])!=" ") UserService(level.users[index]).velocity.x = parseFloat(String(args[1])); if (String(args[2])!=" ") UserService(level.users[index]).velocity.y = parseFloat(String(args[2])); // Update coordinates of player //UserService(level.users[index]).widget.x = parseFloat(String(args[3])); //UserService(level.users[index]).widget.y = parseFloat(String(args[4])); // Update firing } /** * Remote handler that accepts mouse input changes * @param msg (String) a "|" separated list of arguments. Ex: index|mouseX|mouseY */ public function inputMouseChanged(msg : String):void { var args : Array = msg.split("|"); var index : int = parseInt(String(args[0])); var x : Number = parseFloat(String(args[1])); var y : Number = parseFloat(String(args[2])); UserService(level.users[index]).mousePos = new Point(x, y); } // MouseEvent functions protected function mouseMove(e:MouseEvent):void { client.socketSend("Mmousemove:" + user.index + "|" + mouseX + "|" + mouseY); } protected function mouseDown(e:MouseEvent):void { keys['m'] = true; } protected function mouseUp(e:MouseEvent):void { keys['m'] = false; if (gun.ammoLoaded == 0 && !gun.reloading) { gun.reload(); } } // Listens for keyboard down events protected function keyDown(e:KeyboardEvent):void { switch (e.keyCode) { case 65: // A if (!keys['a']) { client.socketSend("Minputchanged:" + user.index + "|" // index + -user.speed + "|" // x-velocity + " |" // y-velocity + user.widget.x + "|" // x-position + user.widget.y + "|" // y-position + keys['f'] || keys['m']); // firing? keys['a'] = true; } break; case 83: // S if (!keys['s']) { client.socketSend("Minputchanged:" + user.index + "| |" + user.speed + "| "); keys['s'] = true; } break; case 68: // D if (!keys['d']) { client.socketSend("Minputchanged:" + user.index + "|" + user.speed + "| | "); keys['d'] = true; } break; case 87: // W if (!keys['w']) { client.socketSend("Minputchanged:" + user.index + "| |" + -user.speed + "| "); keys['w'] = true; } break; case 32: // Space case 70: // F keys['f'] = true; break; case 82: // R if (!gun.reloading) { gun.reload(); } var test : TextField = new TextField(); test.text = "Reloading"; test.x = 0; test.y = stage.stageHeight - 25; test.textColor = 0x00FF00; test.backgroundColor = 0x000000; test.height = 20; test.width = stage.stageWidth - 100; test.background = true; test.alpha = .6; TweenLite.to(test, 10, { alpha:0, onStart:addChild, onStartParams:[test], onComplete:removeChild, onCompleteParams:[test] } ); break; case 9: // Tab switchWeapons(); break; case 80: // P paused = !paused; break; } } // Listens for keyboard up events protected function keyUp(e:KeyboardEvent):void { switch (e.keyCode) { case 65: // A user.velocity.x = 0; if (keys['d']) { client.socketSend("Minputchanged:" + user.index + "|" + user.speed + "| | "); } else { client.socketSend("Minputchanged:" + user.index + "|0| | "); } keys['a'] = false; break; case 83: // S if (keys['w']) { client.socketSend("Minputchanged:" + user.index + "| |" + -user.speed + "| "); } else { client.socketSend("Minputchanged:" + user.index + "| |0| "); } keys['s'] = false; break; case 68: // D user.velocity.x = 0; if (keys['a']) { client.socketSend("Minputchanged:" + user.index + "|" + -user.speed + "| | "); } else { client.socketSend("Minputchanged:" + user.index + "|0| | "); } keys['d'] = false; break; case 87: // W if (keys['s']) { client.socketSend("Minputchanged:" + user.index + "| |" + user.speed + "| "); } else { client.socketSend("Minputchanged:" + user.index + "| |0| "); } keys['w'] = false; break; case 32: // Space case 70: // F keys['f'] = false; if (gun.ammoLoaded == 0 && !gun.reloading) { gun.reload(); } break; case 82: // R break; case 9: // Tab break; } } protected function enterFrame(e:Event):void { if (paused && SystemController.getInstance().singlePlayerMode) return; if (!levelComplete) { if (shot_counter > 0) shot_counter--; frameEvent_UpdateGUI(); frameEvent_UpdatePlayerPosition(); frameEvent_CheckGunFire(); frameEvent_CheckBulletCollision(); frameEvent_CheckEndingConditions(); } } protected function frameEvent_UpdateGUI(): void { widget.weapon_ammo.text = (gun.ammoLoaded + " / " + gun.ammoLeft); widget.weapon_name.text = gun.name; widget.health_amount.x = - user.health; widget.cash_amount.text = "$" + user.money; widget.zombies.text = level.zombies.length + " Zombies Left"; } /** * Updates player location(s) according to velocities and mouse positions defined in UserService(s) */ protected function frameEvent_UpdatePlayerPosition() : void { var i : int; for (i = 0; i < level.users.length; i++) { var curr_user : UserService = UserService(level.users[i]); // Aim the player towards the mouse var position : Point = new Point(curr_user.widget.x + level.widget.x, curr_user.widget.y + level.widget.y); var angle:Number = Math.atan((curr_user.mousePos.y - position.y) / (curr_user.mousePos.x - position.x)); if (curr_user.mousePos.x < position.x) { angle += Math.PI; } curr_user.widget.rotation = angle * 180 / Math.PI; // Move the player var initial : Point = new Point(curr_user.widget.x, curr_user.widget.y); var destination : Point = new Point(curr_user.widget.x + curr_user.velocity.x*1.03, curr_user.widget.y + curr_user.velocity.y*1.03); if (!level.hitTest(initial, destination)) { curr_user.widget.x = destination.x; curr_user.widget.y = destination.y; } } level.pickupTest(); // Center on user var offset : Point = new Point(stage.stageWidth, stage.stageHeight); level.widget.x = Math.min(0, Math.max(-user.widget.x + offset.x/2, -level.width + offset.x)); level.widget.y = Math.min(0, Math.max(-user.widget.y + offset.y/2, -level.height + offset.y)); } /** * Checks for bullets hitting a zombie. * In multiplayer, only this user's bullets undergo collision detection. */ protected function frameEvent_CheckBulletCollision():void { for (var l:int = 0; l < level.zombies.length; l++) { var tempZ:Zombie = Zombie(level.zombies[l]); for ( var m: int = 0; m < bullets.length; m++) { var tempB:Bullet = Bullet(bullets[m]); tempB.lifeTime--; if ((Math.round(tempB.graphic.x) == tempB.endPoint.x) && (Math.round(tempB.graphic.y) == tempB.endPoint.y) || tempB.penetration <= 0 || tempB.lifeTime <= 0) { // Check to remove bullet tempB.graphic.visible = false; bullets.splice(m, 1); user.tallyBullet(tempB); } else if (tempB.graphic.hitTestObject(tempZ.graphic)) { // Check for a hit with a zombie tempB.hit = true; var bloooood:g_blood = new g_blood(); bloooood.rotation = user.widget.rotation + Math.PI; bloooood.x = tempZ.graphic.x; bloooood.y = tempZ.graphic.y; //level.bloodsplatters.addChild(bloooood); TweenLite.to(bloooood, 3, { alpha:0, onStart:level.bloodsplatters.addChild, onStartParams:[bloooood], onComplete:level.bloodsplatters.removeChild, onCompleteParams:[bloooood] } ); client.socketSend("MZreducehealth:" + tempZ.uid + "|" + user.index + "|" + gun.damage); tempB.penetration -= 1; } } } } //Function that switches weapons protected function switchWeapons() : void { if (gun != null) { widget.removeChild(gun.graphic); gun.reloading = false; //sound.localCall(user.name + ":2";} } if (gun == null || gun == user.weaponSecondary) { gun = user.weaponPrimary; sound.localCall(user.index + "|2|"+user.weaponPrimary.type.toString()); } else { gun = user.weaponSecondary; sound.localCall(user.index + "|2|"+user.weaponSecondary.type.toString()); } widget.addChild(gun.graphic); gun.graphic.scaleX = .3; gun.graphic.scaleY = .3; gun.graphic.x = - widget.health_amount.width / 2; gun.graphic.y = 45; } /** * Determines if a user should fire a bullet(s) during this frame. */ protected function frameEvent_CheckGunFire():void { user.widget.shroud.visible = true; if (keys['f'] || keys['m']) { if (shot_counter == 0) { if (gun.reloading && ((gun.ammoLoaded == 0) && (gun.type == 4 || gun.type == 5)) ) { shot_counter = 20; return; } if (gun.ammoLoaded == 0) { shot_counter = Math.floor(Math.random() * 100 % 15) + 10; gun.empty(); } else { //FIRE! gun.reloading = false; user.tallyShot(gun); //SystemController.getInstance().soundService.playShot(); gun.fire(); user.widget.shroud.visible = false; gun.ammoLoaded--; //Calculate hit //TODO MPF uncomment, change to drawBullet(user, angle, gun) //client.socketSend("MPF:" + user.name + "|" + angle + "|" + gun.type); for (var i : int = 0; i < gun.shots; i++) { drawBullet();} alertZombies(); shot_counter = gun.frames_per_bullet; } } } } /* * This function draws a bullet everytime a gun is fired * The characteristics of the bullet are defined by the guns settings * * Current user's bullets -> var bullets : Array; * Other user's bullets -> var multibullets : Array; */ protected function drawBullet():void { //current angle of the mouse in rads var angle:Number = user.widget.rotation * 2 * Math.PI / 360.0; var tempBulletShape : Shape = new Shape(); //temporary bullet shape will be replaced with a graphic later var accuracy : int = Math.random() * 150; //random accuracy value used to determine the guns spread var shotSpread : int = (accuracy % gun.spread); //How much the shot will spread off of the center line var tPoint : Point = Point.polar(5, angle); //temp point for the temp bullet tPoint.x += user.widget.x; tPoint.y += user.widget.y; var tarPoint : Point; //The target point of the shot //Determines if the shot spreads left or right if ((accuracy % 2) == 0) { tarPoint = Point.polar(gun.range, angle + (shotSpread * Math.PI / 180)); } else { tarPoint = Point.polar(gun.range, angle - (shotSpread * Math.PI / 180)); } //Draw the temporary bullet object tempBulletShape.graphics.lineStyle(3, 0x000000, 1, false, LineScaleMode.NONE, CapsStyle.NONE, JointStyle.MITER, 10); tempBulletShape.graphics.moveTo(user.widget.x, user.widget.y); tempBulletShape.graphics.lineTo(tPoint.x, tPoint.y); //initialize the bullet object tarPoint.x = Math.round(tarPoint.x); tarPoint.y = Math.round(tarPoint.y); var testTween: TweenLite = new TweenLite(tempBulletShape, gun.velocity, { x:tarPoint.x, y:tarPoint.y, overwrite:0 } ); var tempBullet : Bullet = new Bullet(gun, tempBulletShape, testTween, tarPoint); TweenLite.delayedCall(gun.velocity, tempBullet.teardown); level.widget.addChild(tempBullet.graphic); bullets.push(tempBullet); var multiShot : String = new String(); multiShot += user.index +"|"+ user.widget.x + "|" + user.widget.y + "|"; multiShot += tPoint.x + "|" + tPoint.y +"|"; multiShot += tarPoint.x + "|" + tarPoint.y + "|"; multiShot += gun.velocity; SystemController.getInstance().clientService.socketSend("Mfire:" + multiShot); } /* * helper function that checks if the zombies are in range to hear a gunshot * if they are flags their detected flag to true. */ private function alertZombies() : void { for (var i : int = 0; i < level.zombies.length; i++) { // For every zombie note the location of the last shot heard var temp : Zombie = Zombie(level.zombies[i]); temp.targetLastShot = new Point(user.widget.x, user.widget.y); } } /** * Checks ending conditions for a single player or multiplayer game */ protected function frameEvent_CheckEndingConditions():void { if (levelComplete) return; if (level.zombies.length == 0) { // User completed the level if (SystemController.getInstance().singlePlayerMode) { user.win_count++; user.level = Math.max(level.levelIndex + 1, user.level); BuyScreen(SystemController.getInstance().screens[ScreenIndex.BUY]).setMessage(BuyScreen.I_SUCCESS); } showEndingStats(true); } for (var i : uint = 0; i < level.users.length; i++) { if (UserService(level.users[i]).health > 0) return; } //All players have health less than or equal to 0 if (SystemController.getInstance().singlePlayerMode) { user.loss_count++; BuyScreen(SystemController.getInstance().screens[ScreenIndex.BUY]).setMessage(BuyScreen.I_FAILED); } showEndingStats(false); } protected function showEndingStats(success : Boolean):void { frameEvent_UpdateGUI(); // so zombie's left = 0 levelComplete = true; user.syncAll(); // Stop player movement stage.removeEventListener(KeyboardEvent.KEY_DOWN, keyDown); stage.removeEventListener(KeyboardEvent.KEY_UP, keyUp); removeEventListener(MouseEvent.MOUSE_DOWN, mouseDown); removeEventListener(MouseEvent.MOUSE_UP, mouseUp); keys['a'] = false; keys['s'] = false; keys['d'] = false; keys['w'] = false; keys['f'] = false; keys['m'] = false; var temp : Number; stats.level_title.text = "Level " + level.levelIndex + " Stats"; // Level Stats stats.l_kill_count.text = (level.numZombies - level.zombies.length).toString(); temp = user.weaponPrimary.level_fired + user.weaponSecondary.level_fired; stats.l_shots.text = temp.toString(); stats.l_hits.text = (temp - user.weaponPrimary.level_misses - user.weaponSecondary.level_misses).toString(); temp = Math.round((temp - user.weaponPrimary.level_misses - user.weaponSecondary.level_misses) * 1000.0 / temp) / 10.0; stats.l_accuracy.text = temp + "%"; stats.weapon1.text = user.weaponPrimary.name + " Accuracy"; stats.w1_shots.text = user.weaponPrimary.level_fired.toString(); stats.w1_hits.text = (user.weaponPrimary.level_fired - user.weaponPrimary.level_misses).toString(); temp = Math.round((user.weaponPrimary.level_fired - user.weaponPrimary.level_misses) * 1000.0 / user.weaponPrimary.level_fired) / 10.0; stats.w1_accuracy.text = temp + "%"; if (isNaN(temp)) { stats.w1_accuracy.text = "0%"; } stats.weapon2.text = user.weaponSecondary.name + " Accuracy"; stats.w2_shots.text = user.weaponSecondary.level_fired.toString(); stats.w2_hits.text = (user.weaponSecondary.level_fired - user.weaponSecondary.level_misses).toString(); temp = Math.round((user.weaponSecondary.level_fired - user.weaponSecondary.level_misses) * 1000.0 / user.weaponSecondary.level_fired) / 10.0; stats.w2_accuracy.text = temp + "%"; if (isNaN(temp)) { stats.w2_accuracy.text = "0%"; } // Player Stats stats.total_kill_count.text = user.kill_count.toString(); stats.total_shots.text = user.shot_count.toString(); stats.total_hits.text = user.hit_count.toString(); stats.total_accuracy.text = Math.round(user.hit_count * 1000.0 / user.shot_count) / 10.0 + "%"; var score : int = user.money + (Math.round(1000.0 * user.hit_count / user.shot_count) * user.kill_count * 69) + user.level * 300; stats.score.text = score.toString(); stats.continue_btn.addEventListener(MouseEvent.CLICK, terminate); stats.x = stage.stageWidth / 2; stats.y = stage.stageHeight / 2; // Reset level vars user.weaponPrimary.level_fired = 0; user.weaponPrimary.level_misses = 0; user.weaponSecondary.level_fired = 0; user.weaponSecondary.level_misses = 0; addChild(stats); } protected function terminate(e : Event = null):void { stats.continue_btn.removeEventListener(MouseEvent.CLICK, terminate); removeEventListener(Event.ENTER_FRAME, enterFrame); music.fadeOut(); SystemController.getInstance().fadeChangeScreen(this, ScreenIndex.BUY); } } }