pxt-common-packages
Version:
Microsoft MakeCode (PXT) common packages
780 lines (700 loc) • 24.9 kB
text/typescript
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/get-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);
}
}