Merge pull request #115 from demipixel/BEHAVIORS

A
This commit is contained in:
Romain Beaumont 2015-11-24 23:41:52 +01:00
commit 5bff660bbe
20 changed files with 752 additions and 217 deletions

View file

@ -20,11 +20,11 @@ This is a list that will keep the developers and the community organized!
- [x] Other dimensions
- [x] Block dropping
- [x] Entity basics
- [x] Plugin System
### To-do
#### Priority
- [ ] Inventories (saving the blocks players get)
- [ ] Plugin System
- [ ] Crafting and Smelting
- [ ] Add in cluster functionality

View file

@ -19,6 +19,7 @@
- [serv.time](#servtime)
- [serv.tickCount](#servtickcount)
- [serv.doDaylightCycle](#servdodaylightcycle)
- [serv.plugins](#servplugins)
- [Events](#events)
- ["error" (error)](#error-error)
- ["clientError" (client,error)](#clienterror-clienterror)
@ -48,21 +49,72 @@
- [server._writeAll(packetName, packetFields)](#server_writeallpacketname-packetfields)
- [server._writeArray(packetName, packetFields, playerArray)](#server_writearraypacketname-packetfields-playerarray)
- [server._writeNearby(packetName, packetFields, loc)](#server_writenearbypacketname-packetfields-loc)
- [Player](#player)
- [Entity](#entity-1)
- [Properties](#properties-1)
- [player.entity](#playerentity)
- [entity.id](#entityid)
- [entity.position](#entityposition)
- [entity.world](#entityworld)
- [entity.type](#entitytype)
- [entity.entityType](#entityentitytype)
- [entity.nearbyEntities](#entitynearbyentities)
- [entity.viewDistance](#entityviewdistance)
- [entity.health](#entityhealth)
- [entity.pitch](#entitypitch)
- [entity.headPitch](#entityheadpitch)
- [entity.yaw](#entityyaw)
- [entity.gravity](#entitygravity)
- [entity.terminalvelocity](#entityterminalvelocity)
- [entity.friction](#entityfriction)
- [entity.size](#entitysize)
- [entity.deathTime](#entitydeathtime)
- [entity.pickupTime](#entitypickuptime)
- [entity.bornTime](#entityborntime)
- [entity.itemId](#entityitemid)
- [entity.itemDamage](#entityitemdamage)
- [entity.metadata](#entitymetadata)
- [entity.nearbyEntities](#entitynearbyentities-1)
- [Events](#events-1)
- [Behaviors](#behaviors)
- [FORMAT](#format)
- ["move"](#move)
- [Methods](#methods-1)
- [entity.getData(pluginName)](#entitygetdatapluginname)
- [entity.getOthers()](#entitygetothers)
- [entity.getOtherPlayers()](#entitygetotherplayers)
- [entity.getNearby()](#entitygetnearby)
- [entity.getNearbyPlayers()](#entitygetnearbyplayers)
- [entity.nearbyPlayers()](#entitynearbyplayers)
- [Low level Methods](#low-level-methods)
- [entity._writeOthers(packetName, packetFields)](#entity_writeotherspacketname-packetfields)
- [entity._writeOthersNearby(packetName, packetFields)](#entity_writeothersnearbypacketname-packetfields)
- [Player](#player)
- [Properties](#properties-2)
- [player.username](#playerusername)
- [player.view](#playerview)
- [player.world](#playerworld)
- [player.nearbyPlayers](#playernearbyplayers)
- [Events](#events-1)
- [Events](#events-2)
- ["connected"](#connected)
- ["spawned"](#spawned)
- ["disconnected"](#disconnected)
- ["chat" (message)](#chat-message)
- ["kicked" (kicker,reason)](#kicked-kickerreason)
- ["positionChanged"](#positionchanged)
- [Methods](#methods-1)
- [Behaviors](#behaviors-1)
- ["move"](#move-1)
- ["look"](#look)
- ["chat"](#chat)
- ["command"](#command)
- ["punch"](#punch)
- ["sendBlock"](#sendblock)
- ["sendChunk"](#sendchunk)
- ["dig"](#dig)
- ["dug"](#dug)
- ["cancelDig"](#canceldig)
- ["forceCancelDig"](#forcecanceldig)
- ["breakAnimation"](#breakanimation)
- ["placeBlock"](#placeblock)
- ["attack"](#attack)
- ["requestRespawn"](#requestrespawn)
- [Methods](#methods-2)
- [player.login()](#playerlogin)
- [player.ban(reason)](#playerbanreason)
- [player.kick(reason)](#playerkickreason)
@ -83,8 +135,6 @@
- [Low level properties](#low-level-properties)
- [player._client](#player_client)
- [Low level methods](#low-level-methods-1)
- [player._writeOthers(packetName, packetFields)](#player_writeotherspacketname-packetfields)
- [player._writeOthersNearby(packetName, packetFields)](#player_writeothersnearbypacketname-packetfields)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -159,6 +209,10 @@ Best to use with modulo (e.g. Something every 10 seconds is `serv.tickCount % 20
Default `true`. If false, time will not automatically pass.
#### serv.plugins
List of all plugins. Use serv.plugins[pluginName] to get a plugin's object and data.
### Events
#### "error" (error)
@ -293,13 +347,227 @@ Writes packet to every player in playerArray
Writes packet to all players within distance of loc. loc has the same paramater as loc in server.getNearby()
## Player
## Entity
Players are a type of entity, so they will have most of the attributes and methods
### Properties
#### player.entity
#### entity.id
The entity of the player, of type `Flying-squid.Entity`
ID of entity on server
#### entity.position
Current position (currently in fixed position (x32 what you'd expect) so do entity.position.scaled(1/32) to get normal position)
#### entity.world
World object entity is in
#### entity.type
Either "player", "mob", or "object" (currently)
#### entity.entityType
Sub-category of entity. For mobs, this is which mob (Zombie/Skeleton, etc). For objects, this is which object (Arrow/Dropped item, etc)
#### entity.nearbyEntities
Nearby entities to this entity
#### entity.viewDistance
How far away entities are loaded/unloaded (used for players ATM)
#### entity.health
How many half-hearts an entity has of health (e.g. Player has 20). Not really used for objects, only players and mobs.
#### entity.pitch
Pitch of entity (rotation sideways)
#### entity.headPitch
Pitch of entity's head
#### entity.yaw
Yaw of entity (rotation looking up and down)
#### entity.gravity
Gravity of entity (non-players) to calculate physics.
#### entity.terminalvelocity
Only applies to gravity, really. You can still apply a velocity larger than terminal velocity.
#### entity.friction
Decreases velocity when touching blocks
#### entity.size
Used to calculate collisions for server-side entities
#### entity.deathTime
How much time before an entity despawns (in ms)
#### entity.pickupTime
How long before an entity can be picked up (in ms)
#### entity.bornTime
When an entity was born. Used with pickupTime and deathTime (time in epoch)
#### entity.itemId
If a block drop, what item id
#### entity.itemDamage
If a block drop, what item damage
#### entity.metadata
Metadata for the entity (not like block metadata/damage). Contains stuff like NBT.
#### entity.nearbyEntities
List of entities that the entity believes is nearby.
### Events
### Behaviors
Behaviors are very interesting. Let me explain to you how they work:
Behaviors are a special type of event. They are editable and allow defaults to be cancellable making the powerful
for plugins to take control of and interact with each other. Three different events get called
for a behavior:
- EVENTNAME_cancel
- EVENTNAME
- EVENTNAME_done
EVENTNAME_cancel passses the paramaters `data` (object of all info about behavior. Changing the data could have effects on outcome) and `cancel`, a function. This event is run before the default action. If `cancel()` is called, it will cancel the default action. More on this later.
EVENTNAME passes `data` as well as `cancelled` so plugins can check if the default behavior has been cancelled. This is event is run
before the default action.
EVENTNAME_done passes `data` and `cancelled`. This event is run before the default action.
Example: One plugin wants to cancel a player's movement while another wants to say "HI" when they move
Plugin A:
```js
player.on('move_cancel', ({position}, cancel) => {
cancel(); // If player tries to move, shoots them back where they came from
});
```
Plugin B:
```js
player.on('move', ({position}, cancelled) => {
if (!cancelled) player.chat('HI!');
})
```
When a player normally moves, the server saves their position and sends it to all clients. Therefore, if a "move" behavior was truly cancelled,
the player would be able to move freely while the server and other players would see the player stationary. This doesn't happen because
behaviors can have "default cancel functions". In the case of a player's "move", the default cancel function sends them back where they
came from. To prevent this from happening, use the "preventDefaultCancel" paramater: cancel(false);
Plugin C
```js
player.on('move_cancel', ({position}, cancel) => {
cancel(false); // Doesn't teleport player back
});
```
If we keep Plugin B and replace Plugin A with Plugin C, we'll see that the player can move freely but will not recieve the
word "HI" and other players will be unable to see their movements.
Finally, there is hidden cancel. This is the second paramater in cancel, and allows plugins to hide the fact that they cancelled
the default action from other plugins. It's best not to use this, but I know somebody will someday need this.
Plugin D
```js
player.on('move_cancel', ({position}, cancel) => {
cancel(false, true); // Player doesn't teleport back and now "cancelled" will be false
})
```
Using Plugin B and D together, the player will be able to move freely and will be spammed with "HI", however the server will not store
their position and other players will not see the player move.
#### FORMAT
Defition of behavior.
- var1: Variable with value, can be changed (default: defaultValue)
- var2 (u): Variable with value. You can change it however it will not have any effect on the defautl action (and could screw with other plugins, watch out!). U stands for unused
Default: What happens if this isn't cancelled.
Cancelled: What happens if this is cancelled and preventDefaultCancel is still false.
#### "move"
Emitted when server calculates new position for the entity (DOES NOT APPLY TO PLAYER!)
- old (u): Where the entity came from
- onGround (u): If the entity is on the ground
Default: Send entity relative-move or teleport packets to all nearby players
Cancelled: Set entity position to old position
### Methods
#### entity.getData(pluginName)
Gets object that stores data, personalized per plugin. Returns null if plugin does not exist.
Shortcut for: entity.pluginData[pluginName];
#### entity.getOthers()
Get every other entity other than self
#### entity.getOtherPlayers()
Gets every player other than self (all players if entity is not a player)
#### entity.getNearby()
Gets all entities nearby (within entity.viewDistance)
#### entity.getNearbyPlayers()
Gets all nearby players regardless of what client thinks
#### entity.nearbyPlayers()
Gets all nearby players that client can see
### Low level Methods
#### entity._writeOthers(packetName, packetFields)
Writes to all other players on server
#### entity._writeOthersNearby(packetName, packetFields)
Writes to all players within viewDistance
## Player
### Properties
#### player.username
@ -309,14 +577,6 @@ The username of the player
The view size of the player, for example 8 for 16x16
#### player.world
The world which the player is in.
#### player.nearbyPlayers
Nearby players.
### Events
#### "connected"
@ -343,6 +603,176 @@ Fires when the player says `message`.
fires when the position changes in small amounts (walking, running, or flying)
### Behaviors
See entity "Behaviors" for more info
#### "move"
When player tries to move
- position (u): New position player is trying to move to
- onGround (u): Whether player thinks they're on the ground or not
Default: Save position/onGround and write to all nearby players
Cancelled: Snap back to old position
#### "look"
When player tries to look somewhere
- yaw (u): New yaw player is looking
- pitch (u): New pitch player is looking
- onGround (u): If player thinks they're on the ground
Default: Save look directions, send to all nearby players
Cancelled: Snap their view back to old yaw and pitch
#### "chat"
Emitted when player tries to say something (unless they're message starts with /, then refer to "command")
- message (u): Message player sent
- broadcastMessage: What is put in server chat (Default: <username> message)
Default: Broadcasts to server their message
Cancelled: Nothing
#### "command"
Emitted when player starts their message with a slash
- command: Their commands (excludes the slash)
Default: Handle command by command system
Cancelled: Nothing
#### "punch"
When player tries to punch nothing
Default: Send punch animation to nearby players
Cancelled: Nothing
#### "sendBlock"
Emitted when sending a block to a player (block changed). This is separate for every player, cancelling this for one player causes ghost blocks!
- position: Position of the block
- id: ID of the block
- data: Metadata of the block
Default: Send block change to player.
Cancelled: Nothing
#### "sendChunk"
Emitted when sending a chunk to a player (loading it in)
- x: Chunk X
- z: Chunk Z
- chunk: Chunk data
Default: Continue sending chunk to client
Cancelled: Nothing
#### "dig"
Emitted when any player STARTS digging (i.e. survival only)
- position: Position of block being mined
- block (u): Block being mined
Default: Allow player to start mining block, send changes in break animation to other players
Cancelled: Stop them from digging
#### "dug"
Emitted when a player finishes digging something (or a player in creative breaks a block)
- position: Position of block dug
- block (u): Block dug
- dropBlock: Should it drop a block object (Default: false in creative, otherwise true)
- blockDropPosition: Where block is dropped (Default: center of block)
- blockDropWorld: World block is dropped in (Default is the world the player/block is in)
- blockDropVelocity: The velocity the block has when dropped (Default: random)
- blockDropId: ID of the block dropped
- blockDropDamage: Damage of the block dropped
- blockDropPickup: Time before user can pick up the block (Default: 0.5 seconds)
- blockDropDeath: Time before item despawns (Default: 5 minutes)
Default: Save new block as air, sends to all nearby players
Cancelled: Send to player the block that was there
#### "cancelDig"
Emitted when a player cancels digging in the middle (i.e. survival only)
- position: Position of block that was being mined
- block (u): Block that was being mined
Default: Stop animation for all players, save stop digging
Cancelled: Nothing
#### "forceCancelDig"
Emitted when the server cancels a dig (currently only happens if the player mines too fast)
- stop: Whether the digging should be cancelled because they mined too fast (Default: true)
- start (u): Time mining started
- time (u): How long the player has been mining
##### "breakAnimation"
Emitted when the server believes the break animation should increase (not sent by client!)
- position: Position of block being updated
- state: New state being changed to
- lastState (u): Last state of block
- start (u): When mining started
- timePassed (u): How long between start and now
Default: Send animation to everyone
Cancelled: Nothing
#### "placeBlock"
Emitted when a player places a block
- position: Position they're attempting to place the block
- id: Id of block being placed
- damage: Data of block being placed
- reference (u): Reference block that was placed on
- direction (u): Direction vector from reference to position
- playSound: Which sound to play (Default: true)
- sound: Sound to play (Default: default sound for that material)
Default: Place block for server and nearby players
Cancelled: Replace block with old block for player
#### "attack"
Emitted when a player attacks an entity
- attackedEntity: Entity being attacked
- playSound: Play sound (Default: true)
- sound: Sound to play (default is game.player.hurt)
- damage: Damage to deal (default is based off player's weapon, player's potions, attackEntity's potions, and attackedEntity armor)
- velocity: Which way should attackedEntity move when hit
- maxVelocity: maxVelocity from consecutive hits
- animation: Play death/hit animation
Default: Damage entity, play sound, send velocity, play animation for death/hit
Cancelled: Nothing
#### "requestRespawn"
Emitted when a player tries to respawn
Default: Let them respawn
Cancelled: Nothing. You monster.
### Methods
#### player.login()
@ -430,10 +860,4 @@ The internal implementation to communicate with a client
### Low level methods
#### player._writeOthers(packetName, packetFields)
write to other players than `player` the packet `packetName` with fields `packetFields`
#### player._writeOthersNearby(packetName, packetFields)
write to other players in same world that are within 150 blocks (see player.getNearby())
Same as entity

View file

@ -23,6 +23,7 @@
},
"dependencies": {
"babel-runtime": "^5.4.4",
"emit-then": "^1.0.2",
"minecraft-data": "0.7.0",
"minecraft-protocol": "0.16.2",
"mkdirp": "0.5.1",
@ -32,10 +33,10 @@
"prismarine-block": "0.1.0",
"prismarine-chunk": "0.2.1",
"prismarine-entity": "0.1.0",
"prismarine-world": "0.3.3",
"prismarine-world-sync": "0.1.0",
"prismarine-item": "0.0.0",
"prismarine-windows": "0.0.0",
"prismarine-world": "0.3.3",
"prismarine-world-sync": "0.1.0",
"random-seed": "^0.2.0",
"request-promise": "^0.4.3",
"requireindex": "~1.0.0",

View file

@ -3,6 +3,7 @@ var EventEmitter = require('events').EventEmitter;
var path = require('path');
var requireIndex = require('requireindex');
var plugins = requireIndex(path.join(__dirname, 'lib', 'plugins'));
require('emit-then').register();
if (process.env.NODE_ENV === 'dev'){
require('longjohn');
}

View file

@ -1,21 +1,25 @@
module.exports = (obj) => {
return async (eventName, data, func, opt) => {
return async (eventName, data, func, cancelFunc) => {
var hiddenCancelled = false;
var cancelled = false;
var cancelCount = 0;
var cancel = (hidden) => { // Hidden shouldn't be used often but it's not hard to implement so meh
var defaultCancel = true;
var cancel = (dC=true, hidden=false) => { // Hidden shouldn't be used often but it's not hard to implement so meh
if (hidden) hiddenCancelled = true;
else {
cancelled = true;
cancelCount++;
}
defaultCancel = dC;
}
await obj.emit(eventName + '_cancel', data, cancel);
await obj.emit(eventName, data, cancelled, cancelCount);
await obj.emitThen(eventName + '_cancel', data, cancel).catch((err)=> setTimeout(() => {throw err;},0));
await obj.emitThen(eventName, data, cancelled, cancelCount).catch((err)=> setTimeout(() => {throw err;},0));
if (!hiddenCancelled && !cancelled) await func(data);
if (!hiddenCancelled && !cancelled) await func(data).catch((err)=> setTimeout(() => {throw err;},0));
else if (cancelFunc && defaultCancel) await cancelFunc(data).catch((err)=> setTimeout(() => {throw err;},0));
await obj.emit(eventName + '_done', data, cancelled);
await obj.emitThen(eventName + '_done', data, cancelled).catch((err)=> setTimeout(() => {throw err;},0));
return data;
}
}

View file

@ -1,10 +1,14 @@
module.exports.player=function(player)
{
player._client.on("arm_animation", () =>
player._writeOthersNearby("animation", {
entityId: player.id,
animation: 0
}));
player.behavior('punch', {}, () => {
player._writeOthersNearby("animation", {
entityId: player.id,
animation: 0
});
})
);
player._client.on("entity_action", ({actionId} = {}) => {
if(actionId == 3) {

View file

@ -13,10 +13,17 @@ module.exports.player=function(player,serv)
};
player.sendBlock = (position, blockType, blockData) => // Call from player.setBlock unless you want "local" fake blocks
player._client.write("block_change",{
player.behavior('sendBlock', {
position: position,
id: blockType,
data: blockData
}, ({position, id, damage}) => {
player._client.write("block_change",{
location:position,
type:blockType<<4 | blockData
});
});
player.setBlock = (position,blockType,blockData) => serv.setBlock(player.world,position,blockType,blockData);

View file

@ -11,12 +11,19 @@ module.exports.player=function(player,serv)
{
player._client.on('chat', ({message} = {}) => {
if(message[0]=="/") {
var command = message.slice(1);
player.handleCommand(command);
player.behavior('command', {
command: message.slice(1)
}, ({command}) => {
player.handleCommand(command);
});
}
else {
serv.broadcast('<' + player.username + '>' + ' ' + message);
player.emit("chat",message);
player.behavior('chat', {
message: message,
broadcastMessage: '<' + player.username + '>' + ' ' + message
}, ({message, broadcast, broadcastMessage}) => {
serv.broadcast(broadcastMessage);
});
}
});

View file

@ -75,6 +75,6 @@ module.exports.player=function(player) {
player.handleCommand = (str) => {
player.commands.use(str);
player.commands.use(str).catch((err)=> setTimeout(() => {throw err;},0));
};
};

View file

@ -14,7 +14,12 @@ module.exports.server=function(serv) {
serv.on('tick', (delta,count) => {
if (!serv.doDaylightCycle) return;
if (count % 20 == 0) {
serv.setTime((serv.time + 20) % 24000); // Vanilla only does it every second
serv.behavior('changeTime', {
old: serv.time,
newTime: serv.time + 20
}, ({newTime}) => {
serv.setTime((serv.time + 20) % 24000); // Vanilla only does it every second
});
}
})
};

View file

@ -2,48 +2,36 @@ var Vec3 = require("vec3").Vec3;
module.exports.player=function(player,serv)
{
function cancelDig({position, block}) {
player.sendBlock(position, block.type, block.metadata);
}
player._client.on("block_dig",({location,status} = {}) => {
var pos=new Vec3(location.x,location.y,location.z);
player.world.getBlock(pos)
.then(block => {
player.behavior('digPacket', {
position: pos,
status: status,
block: block
}, ({position,status,block}) => {
currentlyDugBlock=block;
if(currentlyDugBlock.type==0) return;
if(status==0 && player.gameMode!=1)
player.behavior('dig', { // Start dig survival
position: position,
block: block
}, ({position}) => {
return startDigging(position);
});
else if(status==2)
player.behavior('finishDig', { // Finish dig survival
position: position,
block: block
}, ({position}) => {
return completeDigging(position);
});
else if(status==1)
player.behavior('cancelDig', { // Cancel dig survival
position: position,
block: block
}, ({position}) => {
return cancelDigging(position);
});
else if(status==0 && player.gameMode==1)
player.behavior('dig', { // Start/finish dig creative
position: position,
block: block
}, ({position}) => {
return creativeDigging(position);
});
}
)}
)
currentlyDugBlock=block;
if(currentlyDugBlock.type==0) return;
if(status==0 && player.gameMode!=1)
player.behavior('dig', { // Start dig survival
position: pos,
block: block
}, ({position}) => {
return startDigging(position);
}, cancelDig);
else if(status==2)
completeDigging(pos);
else if(status==1)
player.behavior('cancelDig', { // Cancel dig survival
position: pos,
block: block
}, ({position}) => {
return cancelDigging(position);
});
else if(status==0 && player.gameMode==1)
return creativeDigging(pos);
})
.catch((err)=> setTimeout(() => {throw err;},0))
});
@ -75,12 +63,20 @@ module.exports.player=function(player,serv)
newDestroyState=newDestroyState>9 ? 9 : newDestroyState;
if(newDestroyState!=lastDestroyState)
{
lastDestroyState=newDestroyState;
player._writeOthersNearby("block_break_animation",{
"entityId":currentAnimationId,
"location":location,
"destroyStage":newDestroyState
});
player.behavior('breakAnimation', {
lastState: lastDestroyState,
state: newDestroyState,
start: startDigging,
timePassed: currentDiggingTime,
position: location
}, ({lastState, state}) => {
lastDestroyState=state;
player._writeOthersNearby("block_break_animation",{
"entityId":currentAnimationId,
"location":location,
"destroyStage":state
});
})
}
}
}
@ -99,14 +95,32 @@ module.exports.player=function(player,serv)
{
clearInterval(animationInterval);
var diggingTime=new Date()-startDiggingTime;
var stop = false;
if(expectedDiggingTime-diggingTime<100) {
player.changeBlock(location,0,0);
// Drop block
serv.spawnObject(2, player.world, location.offset(0.5, 0.5, 0.5), {
velocity: new Vec3(Math.random()*4 - 2, Math.random()*2 + 2, Math.random()*4 - 2),
itemId: currentlyDugBlock.type,
itemDamage: currentlyDugBlock.metadata
});
stop = true;
stop = player.behavior('forceCancelDig', {
stop: true,
start: startDiggingTime,
time: diggingTime
}).stop;
}
if(!stop) {
player.behavior('dug', {
position: location,
block: currentlyDugBlock,
dropBlock: true,
blockDropPosition: location.offset(0.5, 0.5, 0.5),
blockDropWorld: player.world,
blockDropVelocity: new Vec3(Math.random()*4 - 2, Math.random()*2 + 2, Math.random()*4 - 2),
blockDropId: currentlyDugBlock.type,
blockDropDamage: currentlyDugBlock.metadata,
blockDropPickup: 500,
blockDropDeath: 60*5*1000
}, (data) => {
player.changeBlock(data.position,0,0);
console.log('dropping',data.dropBlock);
if (data.dropBlock) dropBlock(data);
}, cancelDig)
}
else
{
@ -117,10 +131,34 @@ module.exports.player=function(player,serv)
}
}
function dropBlock({blockDropPosition, blockDropWorld, blockDropVelocity, blockDropId, blockDropDamage, blockDropPickup, blockDropDeath}) {
serv.spawnObject(2, blockDropWorld, blockDropPosition, {
velocity: blockDropVelocity,
itemId: blockDropId,
itemDamage: blockDropDamage,
pickupTime: blockDropPickup,
deathTime: blockDropDeath
});
}
function creativeDigging(location)
{
return player.changeBlock(location,0,0);
player.behavior('dug', {
position: location,
block: currentlyDugBlock,
dropBlock: false,
blockDropPosition: location.offset(0.5, 0.5, 0.5),
blockDropWorld: player.world,
blockDropVelocity: new Vec3(Math.random()*4 - 2, Math.random()*2 + 2, Math.random()*4 - 2),
blockDropId: currentlyDugBlock.type,
blockDropDamage: currentlyDugBlock.metadata,
blockDropPickup: 500,
blockDropDeath: 60*5*1000
}, (data) => {
player.changeBlock(data.position,0,0);
if (data.dropBlock) dropBlock(data);
}, cancelDig);
}
};

View file

@ -25,7 +25,7 @@ module.exports.server=function(serv,options) {
return entity;
};
serv.spawnObject = (type, world, position, {pitch=0,yaw=0,velocity=new Vec3(0,0,0),data=1,itemId,itemDamage=0}={}) => {
serv.spawnObject = (type, world, position, {pitch=0,yaw=0,velocity=new Vec3(0,0,0),data=1,itemId,itemDamage=0,pickupTime=500,deathTime=60*1000}={}) => {
var object = serv.initEntity('object', type, world, position.scaled(32).floored());
object.data = data;
object.velocity = velocity.scaled(32).floored();
@ -35,8 +35,8 @@ module.exports.server=function(serv,options) {
object.terminalvelocity = new Vec3(27*32, 27*32, 27*32);
object.friction = new Vec3(15*32, 0, 15*32);
object.size = new Vec3(0.25*32, 0.25*32, 0.25*32); // Hardcoded, will be dependent on type!
object.deathTime = 60*1000; // 60 seconds
object.pickupTime = 200;
object.deathTime = deathTime;
object.pickupTime = pickupTime;
object.itemId = itemId;
object.itemDamage = itemDamage;
@ -167,7 +167,7 @@ module.exports.entity=function(entity,serv){
};
entity.on("positionChanged",() => {
entity.on("move",() => {
if(entity.position.distanceTo(entity.lastPositionPlayersUpdated)>2*32)
entity.updateAndSpawn();
});
@ -259,7 +259,8 @@ module.exports.entity=function(entity,serv){
entity.collect = (collectEntity) => {
if (entity.type != 'player'){
serv.emit('error', 'Non-player entity (ttype ' + entity.type + ') cannot collect another entity')
console.log('[ERROR] Non-player entity (type ' + entity.type + ') cannot collect another entity');
console.log((new Error()).stack);
return;
}

View file

@ -1,3 +1,5 @@
var fs = require('fs');
module.exports.server = function(serv, settings) {
serv.plugins = {};
serv.pluginCount = 0;
@ -19,15 +21,19 @@ module.exports.server = function(serv, settings) {
}
for (var p in settings.plugins) {
if (settings.plugins[p].disabled) continue;
try {
serv.addPlugin(p, require(p), settings.plugins[p]);
require.resolve(p); // Check if it exists, if not do catch, otherwise jump to bottom
} catch (err) {
try {
serv.addPlugin(p, require('../../plugins/' + p), settings.plugins[p]);
try { // Throw error if cannot find plugin
fs.accessSync('./dist/plugins/' + p);
} catch (err) {
throw new Error('Cannot find plugin "' + p + '"');
}
serv.addPlugin(p, require('../../plugins/' + p), settings.plugins[p]);
return;
}
serv.addPlugin(p, require(p), settings.plugins[p]);
}
for (var p in serv.plugins) {

View file

@ -53,7 +53,7 @@ module.exports.player=function(player,serv)
player.on("disconnected",() => serv.log("[INFO]: " + player.username + ' disconnected'));
player.on("chat", message => serv.log("[INFO] " + '<' + player.username + '>' + ' ' + message));
player.on("chat", ({message}) => serv.log("[INFO] " + '<' + player.username + '>' + ' ' + message));
player.on("kicked",(kicker,reason) =>
serv.log(kicker.username + " kicked " + player.username + (reason ? " (" + reason + ")" : "")));

View file

@ -26,15 +26,15 @@ module.exports.player=function(player,serv)
position: placedPosition,
reference: referencePosition,
playSound: true,
sound: 'dig.' + (materialToSound[blocks[heldItem.blockId].material] || 'stone')
}, ({direction, heldItem, position, reference, playSound, sound}) => {
sound: 'dig.' + (materialToSound[blocks[heldItem.blockId].material] || 'stone'),
}, ({direction, heldItem, position, reference, playSound, sound, id, damage}) => {
if (playSound) {
serv.playSound(sound, player.world, placedPosition.clone().add(new Vec3(0.5, 0.5, 0.5)), {
pitch: 0.8
});
}
if(heldItem.blockId!=323){
player.changeBlock(position,heldItem.blockId,heldItem.itemDamage);
player.changeBlock(position, id, damage);
}else if(direction==1){
player.setBlock(position, 63, 0);
player._client.write('open_sign_entity', {
@ -46,6 +46,10 @@ module.exports.player=function(player,serv)
location:position
});
}
}, async () => {
var id = await player.world.getBlockType(placedPosition);
var damage = await player.world.getBlockData(placedPosition);
player.sendBlock(placedPosition, id, damage);
});
});
};

View file

@ -17,22 +17,31 @@ module.exports.player=function(player,serv)
var attackedEntity = serv.entities[entityId];
if(!attackedEntity || (attackedEntity.gameMode != 0 && attackedEntity.type == 'player')) return;
attackedEntity.updateHealth(attackedEntity.health - 1);
serv.playSound('game.player.hurt', player.world, attackedEntity.position.scaled(1/32));
player.behavior('attack', {
attackedEntity: attackedEntity,
sound: 'game.player.hurt',
playSound: true,
damage: 1,
velocity: attackedEntity.position.minus(player.position).plus(new Vec3(0, 0.5, 0)).scaled(5),
maxVelocity: new Vec3(4, 4, 4),
animation: true
}, ({attackedEntity, sound, playSound, damage, velocity, maxVelocity, animation}) => {
attackedEntity.updateHealth(attackedEntity.health - damage);
serv.playSound(sound, player.world, attackedEntity.position.scaled(1/32));
var attackVelocity = attackedEntity.position.minus(player.position).plus(new Vec3(0, 0.5, 0)).scaled(5/32);
attackedEntity.sendVelocity(attackVelocity, new Vec3(4, 4, 4));
attackedEntity.sendVelocity(velocity.scaled(1/32), maxVelocity);
if(attackedEntity.health<=0)
attackedEntity._writeOthers('entity_status',{
if(attackedEntity.health<=0 && animation)
attackedEntity._writeOthers('entity_status',{
entityId:attackedEntity.id,
entityStatus:3
});
else if (animation)
attackedEntity._writeOthers('animation',{
entityId:attackedEntity.id,
entityStatus:3
animation:1
});
else
attackedEntity._writeOthers('animation',{
entityId:attackedEntity.id,
animation:1
});
});
}
player._client.on("use_entity", ({mouse,target} = {}) => {

View file

@ -2,16 +2,18 @@ module.exports.player=function(player)
{
player._client.on("client_command", ({payload}) => {
if(payload == 0) {
player._client.write("respawn",{
dimension:0,
difficulty:0,
gamemode:player.gameMode,
levelType:'default'
player.behavior('requestRespawn', {}, () => {
player._client.write("respawn",{
dimension:0,
difficulty:0,
gamemode:player.gameMode,
levelType:'default'
});
player.sendPosition();
player.updateHealth(20);
player.nearbyEntities=[];
player.updateAndSpawn();
});
player.sendPosition();
player.updateHealth(20);
player.nearbyEntities=[];
player.updateAndSpawn();
}
});
};

View file

@ -40,30 +40,27 @@ module.exports.player=function(player,serv) {
serv.playSound(sound, player.world, null, opt);
};
player._client.on('block_place', ({location}={}) => {
player.on('placeBlock_cancel', async ({position, reference}, cancel) => {
if (player.crouching) return;
var pos=new Vec3(location.x,location.y,location.z);
player.world.getBlockType(pos).then((id) => {
if (id != 25) return;
if (!player.world.blockEntityData[pos.toString()]) player.world.blockEntityData[pos.toString()] = {};
var data = player.world.blockEntityData[pos.toString()];
if (typeof data.note == 'undefined') data.note = -1;
data.note++;
data.note %= 25;
serv.playNoteBlock(data.note, player.world, pos);
}).catch((err)=> setTimeout(() => {throw err;},0));
var id = await player.world.getBlockType(reference);
if (id != 25) return;
cancel(false);
if (!player.world.blockEntityData[reference.toString()]) player.world.blockEntityData[reference.toString()] = {};
var data = player.world.blockEntityData[reference.toString()];
if (typeof data.note == 'undefined') data.note = -1;
data.note++;
data.note %= 25;
serv.playNoteBlock(data.note, player.world, reference);
});
player._client.on('block_dig', ({location,status} = {}) => {
if (status != 0 || player.gameMode == 1) return;
var pos=new Vec3(location.x,location.y,location.z);
player.world.getBlockType(pos).then((id) => {
if (id != 25) return;
if (!player.world.blockEntityData[pos.toString()]) player.world.blockEntityData[pos.toString()] = {};
var data = player.world.blockEntityData[pos.toString()];
if (typeof data.note == 'undefined') data.note = 0;
serv.playNoteBlock(data.not,player.world, pos, data.note);
}).catch((err)=> setTimeout(() => {throw err;},0));
player.on('dig_cancel', async ({position}, cancel) => {
var id = await player.world.getBlockType(position);
if (id != 25) return;
cancel(false);
if (!player.world.blockEntityData[position.toString()]) player.world.blockEntityData[position.toString()] = {};
var data = player.world.blockEntityData[position.toString()];
if (typeof data.note == 'undefined') data.note = 0;
serv.playNoteBlock(data.note ,player.world, position);
});

View file

@ -17,21 +17,29 @@ module.exports.player=function(player)
}
function sendLook(yaw,pitch,onGround)
{
var convYaw=conv(yaw);
var convPitch=conv(pitch);
if (convYaw == player.yaw && convPitch == player.pitch) return;
player._writeOthersNearby("entity_look", {
entityId: player.id,
yaw: convYaw,
pitch: convPitch,
player.behavior('look', {
yaw: yaw,
pitch: pitch,
onGround: onGround
});
player.yaw = convYaw;
player.pitch = convPitch;
player.onGround = onGround;
player._writeOthersNearby("entity_head_rotation", {
entityId: player.id,
headYaw: convYaw
}, () => {
var convYaw=conv(yaw);
var convPitch=conv(pitch);
if (convYaw == player.yaw && convPitch == player.pitch) return;
player._writeOthersNearby("entity_look", {
entityId: player.id,
yaw: convYaw,
pitch: convPitch,
onGround: onGround
});
player.yaw = convYaw;
player.pitch = convPitch;
player.onGround = onGround;
player._writeOthersNearby("entity_head_rotation", {
entityId: player.id,
headYaw: convYaw
});
}, () => {
player.sendPosition();
});
}
@ -44,33 +52,39 @@ module.exports.player=function(player)
});
function sendRelativePositionChange(newPosition, onGround) {
if (player.position.distanceTo(new Vec3(0, 0, 0)) != 0) {
var diff = newPosition.minus(player.position);
if(diff.abs().x>127 || diff.abs().y>127 || diff.abs().z>127)
{
player._writeOthersNearby('entity_teleport', {
entityId:player.id,
x: newPosition.x,
y: newPosition.y,
z: newPosition.z,
yaw: player.yaw,
pitch: player.pitch,
onGround: onGround
});
player.behavior('move', {
onGround: onGround,
position: newPosition
}, () => {
if (player.position.distanceTo(new Vec3(0, 0, 0)) != 0) {
var diff = newPosition.minus(player.position);
if(diff.abs().x>127 || diff.abs().y>127 || diff.abs().z>127)
{
player._writeOthersNearby('entity_teleport', {
entityId:player.id,
x: newPosition.x,
y: newPosition.y,
z: newPosition.z,
yaw: player.yaw,
pitch: player.pitch,
onGround: onGround
});
}
else if (diff.distanceTo(new Vec3(0, 0, 0)) != 0) {
player._writeOthersNearby('rel_entity_move', {
entityId: player.id,
dX: diff.x,
dY: diff.y,
dZ: diff.z,
onGround: onGround
});
}
}
else if (diff.distanceTo(new Vec3(0, 0, 0)) != 0) {
player._writeOthersNearby('rel_entity_move', {
entityId: player.id,
dX: diff.x,
dY: diff.y,
dZ: diff.z,
onGround: onGround
});
}
}
player.position = newPosition;
player.onGround = onGround;
player.emit("positionChanged");
player.position = newPosition;
player.onGround = onGround;
}, () => {
player.sendPosition();
});
}
player.sendPosition = () => {
@ -87,26 +101,31 @@ module.exports.player=function(player)
module.exports.entity=function(entity,serv){
entity.sendPosition = ({oldPos,onGround}) => {
var diff = entity.position.minus(oldPos);
if(diff.abs().x>127 || diff.abs().y>127 || diff.abs().z>127)
entity._writeOthersNearby('entity_teleport', {
entityId: entity.id,
x: entity.position.x,
y: entity.position.y,
z: entity.position.z,
yaw: entity.yaw,
pitch: entity.pitch,
onGround: onGround
});
else if (diff.distanceTo(new Vec3(0, 0, 0)) != 0) serv._writeNearby('rel_entity_move', {
entityId: entity.id,
dX: diff.x,
dY: diff.y,
dZ: diff.z,
entity.behavior('move', {
old: oldPos,
onGround: onGround
}, entity);
}, ({old,onGround}) => {
var diff = entity.position.minus(oldPos);
entity.emit('positionChanged', oldPos);
if(diff.abs().x>127 || diff.abs().y>127 || diff.abs().z>127)
entity._writeOthersNearby('entity_teleport', {
entityId: entity.id,
x: entity.position.x,
y: entity.position.y,
z: entity.position.z,
yaw: entity.yaw,
pitch: entity.pitch,
onGround: onGround
});
else if (diff.distanceTo(new Vec3(0, 0, 0)) != 0) serv._writeNearby('rel_entity_move', {
entityId: entity.id,
dX: diff.x,
dY: diff.y,
dZ: diff.z,
onGround: onGround
}, entity);
}, () => {
entity.position = oldPos;
});
};
};

View file

@ -70,14 +70,20 @@ module.exports.player=function(player,serv) {
player.sendChunk = (chunkX,chunkZ,column) =>
{
player._client.write('map_chunk', {
return player.behavior('sendChunk', {
x: chunkX,
z: chunkZ,
groundUp: true,
bitMap: 0xffff,
chunkData: column.dump()
});
return Promise.resolve();
chunk: column
}, ({x, z, chunk}) => {
player._client.write('map_chunk', {
x: x,
z: z,
groundUp: true,
bitMap: 0xffff,
chunkData: chunk.dump()
});
return Promise.resolve();
})
};
function spiral(arr)