UNPKG

pxt-common-packages

Version:
780 lines (700 loc) 24.9 kB
namespace mp { const MAX_PLAYERS = 4; export enum PlayerNumber { //% block="1" One = 1, //% block="2" Two = 2, //% block="3" Three = 3, //% block="4" Four = 4 } export enum PlayerProperty { //% block="index" Index = 1, //% block="number" Number = 2 } export enum MultiplayerButton { //% block="A" A, //% block="B" B, //% block="up" Up, //% block="right" Right, //% block="down" Down, //% block="left" Left } class ButtonHandler { constructor(public button: MultiplayerButton, public event: ControllerButtonEvent, public handler: (player: Player) => void) { } } class ControllerEventHandler { constructor(public event: ControllerEvent, public handler: (player: Player) => void) { } } class ScoreHandler { constructor(public target: number, public handler: (player: Player) => void) { } } class StateEntry { constructor(public key: number, public value: number) { } } /** * A player in the game */ export class Player { _sprite: Sprite; _state: StateEntry[]; _index: number; _data: any; _mwb: boolean; _vx: number; _vy: number; constructor(index: number) { this._index = index; } get index(): number { return this._index; } get number(): number { return this._index + 1; } get data(): any { if (!this._data) this._data = {}; return this._data; } set data(value: any) { this._data = value; } getProperty(prop: PlayerProperty): number { switch (prop) { case PlayerProperty.Index: return this.index; case PlayerProperty.Number: return this.number; default: return 0; } } getSprite(): Sprite { return this._sprite; } setSprite(sprite: Sprite) { if (sprite && sprite.image) { // Passing 'implicit' flag so we don't override icons that // user has explicitly defined. mp.postPresenceIcon( this.number, sprite.image, /** implicit **/ true ); } else { // Passing 'implicit' flag so we don't override icons that // user has explicitly defined. mp.postPresenceIcon( this.number, undefined, /** implicit **/ true ); } if (this._sprite && this._mwb) { this._getController().stopControllingSprite(this._sprite); } this._sprite = sprite; if (this._sprite && this._mwb) { this._getController().moveSprite(this._sprite, this._vx, this._vy); } } moveWithButtons(vx?: number, vy?: number) { this._mwb = true; this._vx = vx; this._vy = vy; this._getController().moveSprite(this.getSprite(), vx, vy); } getState(key: number): number { if (key === MultiplayerState.score) { return this._getInfo().score(); } if (key === MultiplayerState.life) { return this._getInfo().life(); } return this._getState(key); } setState(key: number, val: number) { if (key === MultiplayerState.score) { this._getInfo().setScore(val); } if (key === MultiplayerState.life) { this._getInfo().setLife(val); } this._setState(key, val); } _setState(key: number, val: number) { this._lookupOrCreateState(key).value = val; } _getState(key: number): number { return this._lookupOrCreateState(key).value; } _getInfo(): info.PlayerInfo { switch (this._index) { case 0: return info.player1; case 1: return info.player2; case 2: return info.player3; case 3: return info.player4; default: return undefined; } } _getController(): controller.Controller { switch (this._index) { case 0: return controller.player1 as any; case 1: return controller.player2; case 2: return controller.player3; case 3: return controller.player4; } return undefined; } _lookupOrCreateState(key: number) { if (!this._state) this._state = []; for (const entry of this._state) { if (entry.key === key) return entry; } const newEntry = new StateEntry(key, 0); this._state.push(newEntry); return newEntry; } } class MPState { players: Player[]; buttonHandlers: ButtonHandler[]; controllerEventHandlers: ControllerEventHandler[]; scoreHandlers: ScoreHandler[]; lifeZeroHandler: (player: Player) => void; indicatorsVisible: boolean; indicatorRenderable: scene.Renderable; constructor() { this.players = []; for (let i = 0; i < MAX_PLAYERS; ++i) this.players.push(new Player(i)); this.buttonHandlers = []; this.controllerEventHandlers = []; this.scoreHandlers = []; this.indicatorsVisible = false; } onButtonEvent(button: MultiplayerButton, event: ControllerButtonEvent, handler: (player: Player) => void) { const existing = this.getButtonHandler(button, event); if (existing) { existing.handler = handler; return; } this.buttonHandlers.push(new ButtonHandler(button, event, handler)); for (const player of this.players) { getButton(player._getController(), button).onEvent(event, () => { this.getButtonHandler(button, event).handler(player); }) } } onControllerEvent(event: ControllerEvent, handler: (player: Player) => void) { const existing = this.getControllerEventHandler(event); if (existing) { existing.handler = handler; return; } this.controllerEventHandlers.push(new ControllerEventHandler(event, handler)); for (const player of this.players) { player._getController().onEvent(event, () => { this.getControllerEventHandler(event).handler(player); }) } } onReachedScore(score: number, handler: (player: Player) => void) { const existing = this.getScoreHandler(score); // Overwrite the existing handler for this score. Last one wins. if (existing) { existing.handler = handler; return; } this.scoreHandlers.push(new ScoreHandler(score, handler)); for (const player of this.players) { player._getInfo().onScore(score, () => { this.getScoreHandler(score).handler(player); }) } } onLifeZero(handler: (player: Player) => void) { if (!this.lifeZeroHandler) { for (const player of this.players) { player._getInfo().onLifeZero(() => { this.lifeZeroHandler(player); }) } } this.lifeZeroHandler = handler; } setPlayerIndicatorsVisible(visible: boolean) { this.indicatorsVisible = visible; if (visible && !this.indicatorRenderable) { this.indicatorRenderable = scene.createRenderable(99, (target, camera) => { if (this.indicatorsVisible) this.drawIndicators(target, camera); }) } } getButtonHandler(button: MultiplayerButton, event: ControllerButtonEvent) { for (const handler of this.buttonHandlers) { if (handler.button === button && handler.event === event) return handler; } return undefined; } getControllerEventHandler(event: ControllerEvent) { for (const handler of this.controllerEventHandlers) { if (handler.event === event) return handler; } return undefined; } getScoreHandler(score: number) { for (const handler of this.scoreHandlers) { if (handler.target === score) return handler; } return undefined; } drawIndicators(target: Image, camera: scene.Camera) { for (const player of this.players) { const sprite = player.getSprite(); if (!sprite || sprite.flags & (sprites.Flag.Destroyed | sprites.Flag.Invisible)) { continue; } let top = Fx.toInt(sprite._hitbox.top) let bottom = Fx.toInt(sprite._hitbox.bottom) let left = Fx.toInt(sprite._hitbox.left) let right = Fx.toInt(sprite._hitbox.right) if (!(sprite.flags & sprites.Flag.RelativeToCamera)) { top -= camera.drawOffsetY; bottom -= camera.drawOffsetY; left -= camera.drawOffsetX; right -= camera.drawOffsetX; } if (left < 0) { const indicator = _indicatorForPlayer(player.number, CollisionDirection.Right); target.drawTransparentImage( indicator, Math.max(right + 2, 0), Math.min( Math.max( (top + ((bottom - top) >> 1) - (indicator.height >> 1)), 0 ), screen.height - indicator.height ) ) } else if (right > 160) { const indicator = _indicatorForPlayer(player.number, CollisionDirection.Left); target.drawTransparentImage( indicator, Math.min(left - indicator.width - 2, screen.width - indicator.width), Math.min( Math.max( (top + ((bottom - top) >> 1) - (indicator.height >> 1)), 0 ), screen.height - indicator.height ) ) } else if (top < 18) { const indicator = _indicatorForPlayer(player.number, CollisionDirection.Bottom); target.drawTransparentImage( indicator, (left + ((right - left) >> 1) - (indicator.width >> 1)), Math.max(bottom + 2, 0) ) } else { const indicator = _indicatorForPlayer(player.number, CollisionDirection.Top); target.drawTransparentImage( indicator, (left + ((right - left) >> 1) - (indicator.width >> 1)), Math.min(top - indicator.height - 2, screen.height - indicator.height) ) } } } } let stateStack: MPState[]; function init() { if (stateStack) return; stateStack = [new MPState()]; game.addScenePushHandler(() => { stateStack.push(new MPState()); }); game.addScenePopHandler(() => { stateStack.pop(); if (stateStack.length === 0) stateStack.push(new MPState()); }); } export function _mpstate() { init(); return stateStack[stateStack.length - 1]; } function getButton(ctrl: controller.Controller, button: MultiplayerButton) { switch (button) { case MultiplayerButton.A: return ctrl.A; case MultiplayerButton.B: return ctrl.B; case MultiplayerButton.Up: return ctrl.up; case MultiplayerButton.Right: return ctrl.right; case MultiplayerButton.Down: return ctrl.down; case MultiplayerButton.Left: return ctrl.left; } } /** * Gets the sprite of the player * @param player The player to get the sprite of * @returns The sprite of the player, or undefined if the player has no assigned sprite */ //% blockId=mp_getPlayerSprite //% block="$player sprite" //% player.shadow=mp_playerSelector //% group=Player //% weight=80 //% blockGap=8 //% help=multiplayer/get-player-sprite //% parts="multiplayer" export function getPlayerSprite(player: Player): Sprite { if (!player) return undefined; return player.getSprite(); } /** * Sets the sprite of the player * @param player The player to set the sprite for * @param sprite The sprite to set */ //% blockId=mp_setPlayerSprite //% block="set $player sprite to $sprite" //% player.shadow=mp_playerSelector //% sprite.shadow=spritescreatenoset //% group=Player //% weight=120 //% blockGap=8 //% help=multiplayer/set-player-sprite //% parts="multiplayer" export function setPlayerSprite(player: Player, sprite: Sprite) { if (!player) return; player.setSprite(sprite); } /** * Selects one of the players by number * @param number The player number * @returns The player */ //% blockId=mp_playerSelector //% block="player $number" //% group=Player //% weight=100 //% blockGap=8 //% help=multiplayer/player-selector //% parts="multiplayer" export function playerSelector(number: PlayerNumber): Player { const index = number - 1; return getPlayerByIndex(index); } /** * Returns an array of all players */ //% blockId=mp_allPlayers //% block="array of all players" //% group=Player //% weight=90 //% blockGap=8 //% help=multiplayer/all-players //% parts="multiplayer" export function allPlayers(): Player[] { return _mpstate().players.slice(); } /** * Gets the player the sprite is assigned to * @param sprite the sprite * @returns Player, or undefined if not found */ //% blockId=mp_getPlayerBySprite //% block="$sprite player" //% sprite.shadow=variables_get //% sprite.defl=mySprite //% group=Player //% weight=70 //% blockGap=8 //% help=multiplayer/get-player-by-sprite //% parts="multiplayer" export function getPlayerBySprite(sprite: Sprite): Player { for (const player of _mpstate().players) { if (player.getSprite() === sprite) return player; } return undefined; } /** * Control a player's sprite with directional buttons * @param player The player to control * @param vx The horizontal velocity of the sprite (optional) * @param vy The vertical velocity of the sprite (optional) */ //% blockId=mp_moveWithButtons //% block="move $player with buttons||vx $vx vy $vy" //% player.shadow=mp_playerSelector //% vx.defl=100 //% vy.defl=100 //% vx.shadow="spriteSpeedPicker" //% vy.shadow="spriteSpeedPicker" //% expandableArgumentMode="toggle" //% inlineInputMode=inline //% group=Controller //% weight=100 //% blockGap=8 //% help=multiplayer/move-with-buttons //% parts="multiplayer" export function moveWithButtons(player: Player, vx?: number, vy?: number) { if (!player) return; player.moveWithButtons(vx, vy); } /** * Runs code when a button on any controller is pressed, released, or held * @param button The button to listen for * @param event The event to listen for (pressed, released, or held) * @param handler The code to run when the button is pressed, released, or held */ //% blockId=mp_onButtonEvent //% block="on $button button $event for $player" //% draggableParameters=reporter //% group=Controller //% weight=90 //% blockGap=8 //% help=multiplayer/on-button-event //% parts="multiplayer" export function onButtonEvent(button: MultiplayerButton, event: ControllerButtonEvent, handler: (player: Player) => void) { _mpstate().onButtonEvent(button, event, handler); } /** * Queries the state of a button on a controller * @param player The player to query * @param button The button to query * @returns true if the button is pressed */ //% blockId=mp_isButtonPressed //% block="is $player $button button pressed" //% player.shadow=mp_playerSelector //% group=Controller //% weight=80 //% blockGap=8 //% help=multiplayer/is-button-pressed //% parts="multiplayer" export function isButtonPressed(player: Player, button: MultiplayerButton): boolean { if (!player) return false; return getButton(player._getController(), button).isPressed(); } /** * Runs code when a controller is connected or disconnected * @param event The event to listen for (controller connected or disconnected) * @param handler Code to run when the event is raised */ //% blockId=mp_onControllerEvent //% block="on $player $event" //% draggableParameters=reporter //% group=Controller //% weight=70 //% blockGap=8 //% help=multiplayer/on-controller-event //% parts="multiplayer" export function onControllerEvent(event: ControllerEvent, handler: (player: Player) => void) { _mpstate().onControllerEvent(event, handler); } /** * Queries the connected state of the player * @param player The player to query * @returns true if the player is connected */ //% blockId=mp_isConnected //% block="$player connected" //% player.shadow=mp_playerSelector //% group=Controller //% weight=60 //% blockGap=8 //% help=multiplayer/is-connected //% parts="multiplayer" export function isConnected(player: Player): boolean { if (!player) return false; return player._getController().connected; } /** * Gets the value of the specified player state * @param player The player to get the state for * @param state The state to get * @returns The value of the state */ //% blockId=mp_getPlayerState //% block="$player $state" //% player.shadow=mp_playerSelector //% state.shadow=mp_multiplayerstate //% group=Info //% weight=100 //% blockGap=8 //% help=multiplayer/get-player-state //% parts="multiplayer" export function getPlayerState(player: Player, state: number): number { if (!player) return 0; return player.getState(state); } /** * Sets the value of the specified player state * @param player The player to set the state for * @param state The state to set * @param value The value to set the state to */ //% blockId=mp_setPlayerState //% block="set $player $state to $value" //% player.shadow=mp_playerSelector //% state.shadow=mp_multiplayerstate //% group=Info //% weight=90 //% blockGap=8 //% help=multiplayer/set-player-state //% parts="multiplayer" export function setPlayerState(player: Player, state: number, value: number) { if (!player) return; player.setState(state, value); } /** * Changes the value of the specified player state * @param player The player to change the state for * @param state The state to change * @param delta The amount to change the state by */ //% blockId=mp_changePlayerStateBy //% block="change $player $state by $delta" //% player.shadow=mp_playerSelector //% state.shadow=mp_multiplayerstate //% delta.defl=1 //% group=Info //% weight=80 //% blockGap=8 //% help=multiplayer/change-player-state-by //% parts="multiplayer" export function changePlayerStateBy(player: Player, state: number, delta: number) { if (!player) return; player.setState(state, player.getState(state) + delta); } /** * Gets a property of the player * @param player The player to get the property of * @param prop The property to get * @returns The value of the property */ //% blockId=mp_getPlayerProperty //% block="$player $prop" //% player.shadow=mp_playerSelector //% group=Info //% weight=100 //% blockGap=8 //% help=multiplayer/get-player-property //% parts="multiplayer" export function getPlayerProperty(player: Player, prop: PlayerProperty): number { if (!player) return 0; return player.getProperty(prop); } /** * Runs code once each time a player's score reaches a given value. * @param score The score to check for, eg: 100 * @param handler The code to run when the score is reached */ //% blockId=mp_onScore //% block="on score $score for $player" //% score.defl=100 //% draggableParameters=reporter //% group=Info //% weight=70 //% blockGap=8 //% help=multiplayer/on-score //% parts="multiplayer" export function onScore(score: number, handler: (player: Player) => void) { _mpstate().onReachedScore(score, handler); } /** * Runs code when a player's number of lives reaches zero * @param handler The code to run when the lives reach zero */ //% blockId=mp_onLifeZero //% block="on life zero for $player" //% draggableParameters=reporter //% group=Info //% weight=60 //% blockGap=8 //% help=multiplayer/on-life-zero //% parts="multiplayer" export function onLifeZero(handler: (player: Player) => void) { _mpstate().onLifeZero(handler); } //% blockId=mp_gameOverPlayerWin //% block="game over $player wins" //% player.shadow=mp_playerSelector //% group=Game //% weight=100 //% blockGap=8 //% help=multiplayer/game-over-player-win //% parts="multiplayer" export function gameOverPlayerWin(player: Player) { if (!player) return; game.gameOverPlayerWin(player.number); } /** * Gets the player by number * @param number The one-based number of the player * @returns Player, or undefined if not found */ //% blockId=mp_getPlayerByNumber //% block="player $number" //% number.shadow=variables_get //% number.defl=number //% group=Utility //% weight=80 //% blockGap=8 //% help=multiplayer/get-player-by-number //% parts="multiplayer" export function getPlayerByNumber(number: number): Player { const index = number - 1; return getPlayerByIndex(index); } /** * Gets the player by index * @param index The zero-based index of the player * @returns Player, or undefined if not found */ //% blockId=mp_getPlayerByIndex //% block="player at $index" //% index.shadow=variables_get //% index.defl=index //% group=Utility //% weight=80 //% blockGap=8 //% help=multiplayer/get-player-by-index //% parts="multiplayer" export function getPlayerByIndex(index: number): Player { if (index < 0 || index >= MAX_PLAYERS) return undefined; return _mpstate().players[index]; } /** * Turns player indicators on or off * @param visible indicator visibility */ //% blockId=mp_setPlayerIndicatorsVisible //% block="set player indicators $visible" //% visible.shadow=toggleOnOff //% visible.defl=true //% group=Utility //% weight=100 //% blockGap=8 //% help=multiplayer/set-player-indicators-visible //% parts="multiplayer" export function setPlayerIndicatorsVisible(visible: boolean) { _mpstate().setPlayerIndicatorsVisible(visible); } }