Skip to content
This repository has been archived by the owner on Sep 2, 2020. It is now read-only.

Commit

Permalink
Two-step login process, so player can provide a name.
Browse files Browse the repository at this point in the history
  • Loading branch information
Stéphan Kochen committed Oct 29, 2010
1 parent 959c01d commit 309bf21
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 63 deletions.
17 changes: 10 additions & 7 deletions src/client/renderer/base.coffee
Expand Up @@ -20,7 +20,7 @@ class BaseRenderer
@soundkit = @world.soundkit @soundkit = @world.soundkit


@canvas = $('<canvas/>').appendTo('body') @canvas = $('<canvas/>').appendTo('body')
@lastCenter = [0, 0] @lastCenter = @world.map.findCenterCell().getWorldCoordinates()


@mouse = [0, 0] @mouse = [0, 0]
@canvas.click (e) => @handleClick(e) @canvas.click (e) => @handleClick(e)
Expand Down Expand Up @@ -64,8 +64,11 @@ class BaseRenderer


# Draw a single frame. # Draw a single frame.
draw: -> draw: ->
{x, y} = @world.player if @world.player
{x, y} = @world.player.fireball.$ if @world.player.fireball? {x, y} = @world.player
{x, y} = @world.player.fireball.$ if @world.player.fireball?
else
x = y = null


# Remember or restore the last center position. We use this after tank # Remember or restore the last center position. We use this after tank
# death, so as to keep drawing something useful while we fade. # death, so as to keep drawing something useful while we fade.
Expand All @@ -87,12 +90,12 @@ class BaseRenderer
@drawOverlay() @drawOverlay()


# Update all DOM HUD elements. # Update all DOM HUD elements.
@updateHud() @updateHud() if @hud


# Play a sound effect. # Play a sound effect.
playSound: (sfx, x, y, owner) -> playSound: (sfx, x, y, owner) ->
mode = mode =
if owner == @world.player then 'Self' if @world.player and owner == @world.player then 'Self'
else else
dx = x - @lastCenter[0]; dy = y - @lastCenter[1] dx = x - @lastCenter[0]; dy = y - @lastCenter[1]
dist = sqrt(dx*dx + dy*dy) dist = sqrt(dx*dx + dy*dy)
Expand Down Expand Up @@ -159,8 +162,8 @@ class BaseRenderer
# Draw HUD elements that overlay the map. These are elements that need to be drawn in regular # Draw HUD elements that overlay the map. These are elements that need to be drawn in regular
# game coordinates, rather than screen coordinates. # game coordinates, rather than screen coordinates.
drawOverlay: -> drawOverlay: ->
unless (player = @world.player).armour == 255 if (player = @world.player) and player.armour != 255
b = @world.player.builder.$ b = player.builder.$
unless b.order == b.states.inTank or b.order == b.states.parachuting unless b.order == b.states.inTank or b.order == b.states.parachuting
@drawBuilderIndicator(b) @drawBuilderIndicator(b)
@drawReticle() @drawReticle()
Expand Down
61 changes: 40 additions & 21 deletions src/client/world/client.coffee
Expand Up @@ -56,15 +56,18 @@ class BoloClientWorld extends ClientWorld
$(@ws).bind 'message.bolo', (e) => $(@ws).bind 'message.bolo', (e) =>
@handleMessage(e.originalEvent) @handleMessage(e.originalEvent)


# Callback after the welcome message was received. # Callback after the server tells us we are synchronized.
receiveWelcome: (tank) -> synchronized: ->
@player = tank
@rebuildMapObjects() @rebuildMapObjects()
@renderer.initHud()
@vignette.destroy() @vignette.destroy()
@vignette = null @vignette = null
@loop.start() @loop.start()


# Callback after the welcome message was received.
receiveWelcome: (tank) ->
@player = tank
@renderer.initHud()

# Send the heartbeat (an empty message) every 10 ticks / 400ms. # Send the heartbeat (an empty message) every 10 ticks / 400ms.
tick: -> tick: ->
super super
Expand Down Expand Up @@ -106,7 +109,7 @@ class BoloClientWorld extends ClientWorld
#### Input handlers. #### Input handlers.


handleKeydown: (e) -> handleKeydown: (e) ->
return unless @ws? return unless @ws and @player
switch e.which switch e.which
when 32 then @ws.send net.START_SHOOTING when 32 then @ws.send net.START_SHOOTING
when 37 then @ws.send net.START_TURNING_CCW when 37 then @ws.send net.START_TURNING_CCW
Expand All @@ -115,7 +118,7 @@ class BoloClientWorld extends ClientWorld
when 40 then @ws.send net.START_BRAKING when 40 then @ws.send net.START_BRAKING


handleKeyup: (e) -> handleKeyup: (e) ->
return unless @ws? return unless @ws and @player
switch e.which switch e.which
when 32 then @ws.send net.STOP_SHOOTING when 32 then @ws.send net.STOP_SHOOTING
when 37 then @ws.send net.STOP_TURNING_CCW when 37 then @ws.send net.STOP_TURNING_CCW
Expand All @@ -124,37 +127,46 @@ class BoloClientWorld extends ClientWorld
when 40 then @ws.send net.STOP_BRAKING when 40 then @ws.send net.STOP_BRAKING


buildOrder: (action, trees, cell) -> buildOrder: (action, trees, cell) ->
return unless @ws? return unless @ws and @player
trees ||= 0 trees ||= 0
@ws.send [net.BUILD_ORDER, action, trees, cell.x, cell.y].join(',') @ws.send [net.BUILD_ORDER, action, trees, cell.x, cell.y].join(',')


#### Network message handlers. #### Network message handlers.


handleMessage: (e) -> handleMessage: (e) ->
@netRestore()
@processingServerMessages = yes
data = decodeBase64(e.data)
pos = 0
length = data.length
error = null error = null
while pos < length if e.data.charAt(0) == '{'
command = data[pos++]
try try
ate = @handleServerCommand command, data, pos @handleJsonCommand JSON.parse(e.data)
catch e
error = e
else
@netRestore()
try
data = decodeBase64(e.data)
pos = 0
length = data.length
@processingServerMessages = yes
while pos < length
command = data[pos++]
ate = @handleBinaryCommand command, data, pos
pos += ate
@processingServerMessages = no
if pos != length
error = new Error("Message length mismatch, processed #{pos} out of #{length} bytes")
catch e catch e
error = e error = e
break
pos += ate
if pos != length
error = new Error("Message length mismatch, processed #{pos} out of #{length} bytes")
if error if error
@failure 'Connection lost (protocol error)' @failure 'Connection lost (protocol error)'
console?.log "Following exception occurred while processing message:", data console?.log "Following exception occurred while processing message:", data
throw error throw error
@processingServerMessages = no


handleServerCommand: (command, data, offset) -> handleBinaryCommand: (command, data, offset) ->
switch command switch command
when net.SYNC_MESSAGE
@synchronized()
0

when net.WELCOME_MESSAGE when net.WELCOME_MESSAGE
[[tank_idx], bytes] = unpack('H', data, offset) [[tank_idx], bytes] = unpack('H', data, offset)
@receiveWelcome @objects[tank_idx] @receiveWelcome @objects[tank_idx]
Expand Down Expand Up @@ -190,6 +202,13 @@ class BoloClientWorld extends ClientWorld
else else
throw new Error "Bad command '#{command}' from server, at offset #{offset - 1}" throw new Error "Bad command '#{command}' from server, at offset #{offset - 1}"


handleJsonCommand: (data) ->
switch data.command
when 'nick'
@objects[data.idx].name = data.nick
else
throw new Error "Bad JSON command '#{data.command}' from server."

#### Helpers #### Helpers


# Fill `@map.pills` and `@map.bases` based on the current object list. # Fill `@map.pills` and `@map.bases` based on the current object list.
Expand Down
18 changes: 17 additions & 1 deletion src/map.coffee
Expand Up @@ -3,7 +3,7 @@
# modules that is useful on it's own. # modules that is useful on it's own.




{floor, min} = Math {round, floor, min} = Math
{MAP_SIZE_TILES} = require './constants' {MAP_SIZE_TILES} = require './constants'




Expand Down Expand Up @@ -450,6 +450,22 @@ class Map
cell.retile() cell.retile()
, sx, sy, ex, ey , sx, sy, ex, ey


# Find the cell at the center of the 'painted' map area.
findCenterCell: ->
t = l = MAP_SIZE_TILES - 1
b = r = 0
@each (c) ->
l = c.x if l > c.x
r = c.x if r < c.x
t = c.y if t > c.y
b = c.y if b < c.y
if l > r
t = l = 0
b = r = MAP_SIZE_TILES - 1
x = round(l + (r - l) / 2)
y = round(t + (b - t) / 2)
@cellAtTile(x, y)

#### Saving and loading #### Saving and loading


# Dump the map to an array of octets in BMAP format. # Dump the map to an array of octets in BMAP format.
Expand Down
1 change: 1 addition & 0 deletions src/net.coffee
Expand Up @@ -17,6 +17,7 @@


# These are the server message identifiers both sides need to know about. # These are the server message identifiers both sides need to know about.
# The server sends binary data (encoded as base64). So we need to compare character codes. # The server sends binary data (encoded as base64). So we need to compare character codes.
exports.SYNC_MESSAGE = 's'.charCodeAt(0)
exports.WELCOME_MESSAGE = 'W'.charCodeAt(0) exports.WELCOME_MESSAGE = 'W'.charCodeAt(0)
exports.CREATE_MESSAGE = 'C'.charCodeAt(0) exports.CREATE_MESSAGE = 'C'.charCodeAt(0)
exports.DESTROY_MESSAGE = 'D'.charCodeAt(0) exports.DESTROY_MESSAGE = 'D'.charCodeAt(0)
Expand Down
109 changes: 75 additions & 34 deletions src/server/application.coffee
Expand Up @@ -35,12 +35,13 @@ class BoloServerWorld extends ServerWorld
constructor: (@map) -> constructor: (@map) ->
super super
@boloInit() @boloInit()
@clients = []
@map.world = this @map.world = this
@oddTick = no @oddTick = no
@spawnMapObjects() @spawnMapObjects()


close: -> close: ->
for {client} in @tanks when client? for client in @clients
client.end() client.end()


#### Callbacks #### Callbacks
Expand All @@ -63,20 +64,14 @@ class BoloServerWorld extends ServerWorld
#### Connection handling. #### Connection handling.


onConnect: (ws) -> onConnect: (ws) ->
tank = @spawn Tank
packet = @changesPacket(yes)
packet = new Buffer(packet).toString('base64')
for {client} in @tanks when client?
client.sendMessage(packet)

# Set-up the websocket parameters. # Set-up the websocket parameters.
tank.client = ws @clients.push ws
ws.setTimeout 10000 # Disconnect after 10s of inactivity. ws.setTimeout 10000 # Disconnect after 10s of inactivity.
ws.heartbeatTimer = 0 ws.heartbeatTimer = 0
ws.on 'message', (message) => @onMessage(tank, message) ws.on 'message', (message) => @onMessage(ws, message)
ws.on 'end', => @onEnd(tank) ws.on 'end', => @onEnd(ws)
ws.on 'error', (exception) => @onError(tank, exception) ws.on 'error', (error) => @onError ws, error
ws.on 'timeout', => @onError(tank, 'Timed out') ws.on 'timeout', => @onError ws, new Error('Connection timed out')


# Send the current map state. We don't send pillboxes and bases, because the client # Send the current map state. We don't send pillboxes and bases, because the client
# receives create messages for those, and then fills the map structure based on those. # receives create messages for those, and then fills the map structure based on those.
Expand All @@ -90,31 +85,35 @@ class BoloServerWorld extends ServerWorld
packet = [] packet = []
for obj in @objects for obj in @objects
packet = packet.concat [net.CREATE_MESSAGE, obj._net_type_idx] packet = packet.concat [net.CREATE_MESSAGE, obj._net_type_idx]
packet = packet.concat [net.UPDATE_MESSAGE], @dumpTick(yes) packet = packet.concat [net.UPDATE_MESSAGE], @dumpTick(yes), [net.SYNC_MESSAGE]
packet = packet.concat pack('BH', net.WELCOME_MESSAGE, tank.idx)
packet = new Buffer(packet).toString('base64') packet = new Buffer(packet).toString('base64')
ws.sendMessage(packet) ws.sendMessage(packet)


onEnd: (tank) -> onEnd: (ws) ->
return unless ws = tank.client
tank.client = null
ws.end() ws.end()
@onDisconnect(tank) @onDisconnect(ws)


onError: (tank, exception) -> onError: (ws, error) ->
return unless ws = tank.client console.log error.toString()
tank.client = null
# FIXME: log exception
ws.destroy() ws.destroy()
@onDisconnect(tank) @onDisconnect(ws)


onDisconnect: (tank) -> onDisconnect: (ws) ->
@destroy tank @destroy ws.tank if ws.tank

ws.tank = null
onMessage: (tank, message) -> if (idx = @clients.indexOf(ws)) != -1
return unless tank.client? @clients.splice(idx, 1)
switch message.charAt(0)
when '' then tank.client.heartbeatTimer = 0 onMessage: (ws, message) ->
if message == '' then ws.heartbeatTimer = 0
else if message.charAt(0) == '{' then @onJsonMessage(ws, message)
else @onSimpleMessage(ws, message)

onSimpleMessage: (ws, message) ->
unless tank = ws.tank
return @onError ws, new Error("Received a game command from a spectator")
command = message.charAt(0)
switch command
when net.START_TURNING_CCW then tank.turningCounterClockwise = yes when net.START_TURNING_CCW then tank.turningCounterClockwise = yes
when net.STOP_TURNING_CCW then tank.turningCounterClockwise = no when net.STOP_TURNING_CCW then tank.turningCounterClockwise = no
when net.START_TURNING_CW then tank.turningClockwise = yes when net.START_TURNING_CW then tank.turningClockwise = yes
Expand All @@ -132,13 +131,55 @@ class BoloServerWorld extends ServerWorld
trees = parseInt(trees); x = parseInt(x); y = parseInt(y) trees = parseInt(trees); x = parseInt(x); y = parseInt(y)
builder = tank.builder.$ builder = tank.builder.$
if trees < 0 or not builder.states.actions.hasOwnProperty(action) if trees < 0 or not builder.states.actions.hasOwnProperty(action)
@onError(tank, 'Received invalid build order') @onError ws, new Error("Received invalid build order")
else else
builder.performOrder action, trees, @map.cellAtTile(x, y) builder.performOrder action, trees, @map.cellAtTile(x, y)
else @onError(tank, 'Received an unknown command') else
sanitized = command.replace(/\W+/, '')
@onError ws, new Error("Received an unknown command: #{sanitized}")

onJsonMessage: (ws, message) ->
try
message = JSON.parse(message)
catch e
return @onError ws, e
switch message.command
when 'join'
if ws.tank
@onError ws, new Error("Client tried to join twice.")
else if typeof(message.nick) != 'string' or message.nick.length > 40
@onError ws, new Error("Client specified invalid nickname.")
else
@createPlayer(ws, message.nick)
else
sanitized = message.command.slice(0, 10).replace(/\W+/, '')
@onError ws, new Error("Received an unknown JSON command: #{sanitized}")


#### Helpers #### Helpers


# Simple helper to send a message to everyone.
broadcast: (message) ->
for client in @clients
client.sendMessage(message)

# Creates a tank for a connection and synchronizes it to everyone. Then tells the connection
# that this new tank is his.
createPlayer: (ws, name) ->
ws.tank = @spawn Tank
packet = @changesPacket(yes)
packet = new Buffer(packet).toString('base64')
@broadcast packet

ws.tank.name = name
@broadcast JSON.stringify
command: 'nick'
idx: ws.tank.idx
nick: name

packet = pack('BH', net.WELCOME_MESSAGE, ws.tank.idx)
packet = new Buffer(packet).toString('base64')
ws.sendMessage(packet)

# We send critical updates every frame, and non-critical updates every other frame. On top of # We send critical updates every frame, and non-critical updates every other frame. On top of
# that, non-critical updates may be dropped, if the client's hearbeats are interrupted. # that, non-critical updates may be dropped, if the client's hearbeats are interrupted.
sendPackets: -> sendPackets: ->
Expand All @@ -152,7 +193,7 @@ class BoloServerWorld extends ServerWorld
smallPacket = new Buffer(smallPacket).toString('base64') smallPacket = new Buffer(smallPacket).toString('base64')
largePacket = new Buffer(largePacket).toString('base64') largePacket = new Buffer(largePacket).toString('base64')


for {client} in @tanks when client? for client in @clients
if client.heartbeatTimer > 40 if client.heartbeatTimer > 40
client.sendMessage(smallPacket) client.sendMessage(smallPacket)
else else
Expand Down

0 comments on commit 309bf21

Please sign in to comment.