diff --git a/coffee/bot.coffee b/coffee/bot.coffee index 7f741f6..41e3b2a 100644 --- a/coffee/bot.coffee +++ b/coffee/bot.coffee @@ -38,7 +38,7 @@ class Bot extends Pushable @addAction new KikiAction @, ACTION_JUMP, "jump", 120 @addAction new KikiAction @, ACTION_JUMP_FORWARD, "jump forward", 200 @addAction new KikiAction @, ACTION_FALL_FORWARD, "fall forward", 200 - @addAction new KikiAction @, ACTION_SHOOT, "shoot", 200, KikiAction::REPEAT + @addAction new KikiAction @, ACTION_SHOOT, "shoot", 200, KikiAction.REPEAT @getActionWithId(ACTION_FALL).setDuration 120 @addEventWithName "died" @@ -93,34 +93,34 @@ class Bot extends Pushable when ACTION_FALL_FORWARD then newPos += @getDown() + @getDir() when ACTION_FALL if @direction != KVector() - KikiPushable::initAction action + KikiPushable.initAction action return else newPos += @getDown() break else - KikiPushable::initAction (action) + KikiPushable.initAction (action) return # if newPos != @position # world.objectWillMoveToPos (@, newPos, action.getDuration()) - performAction: ( action ) -> + performAction: (action) -> actionId = action.getId() relTime = action.getRelativeTime() dltTime = action.getRelativeDelta() switch actionId when ACTION_SHOOT - if (relTime == 0) - KikiBullet::shootFromBot (@) + if relTime == 0 + KikiBullet.shootFromBot (@) when ACTION_NOOP then return when ACTION_FORWARD - left_tire_rot += dir_sgn * dltTime - right_tire_rot += dir_sgn * dltTime + @left_tire_rot += dir_sgn * dltTime + @right_tire_rot += dir_sgn * dltTime @current_position = @position + relTime * @getDir() return @@ -132,8 +132,8 @@ class Bot extends Pushable when ACTION_JUMP_FORWARD - left_tire_rot += Math.cos(Math.PI/2 - Math.PI/2 * dltTime) - right_tire_rot += Math.cos(Math.PI/2 - Math.PI/2 * dltTime) + @left_tire_rot += Math.cos(Math.PI/2 - Math.PI/2 * dltTime) + @right_tire_rot += Math.cos(Math.PI/2 - Math.PI/2 * dltTime) @current_position = @position + (1.0 - Math.cos(Math.PI/2 * relTime)) * @getDir() + Math.cos(Math.PI/2 - Math.PI/2 * relTime) * @getUp() return @@ -144,106 +144,106 @@ class Bot extends Pushable when ACTION_FALL - if direction != KVector() - KikiPushable::performAction action + if @direction != KVector() + KikiPushable.performAction action return @current_position = @position + relTime * @getDown() return when ACTION_CLIMB_UP - left_tire_rot += dir_sgn * dltTime/2 - right_tire_rot += dir_sgn * dltTime/2 - climb_orientation = KQuaternion::rotationAroundVector(dir_sgn * relTime * -90.0, KVector(1,0,0)) + @left_tire_rot += dir_sgn * dltTime/2 + @right_tire_rot += dir_sgn * dltTime/2 + @climb_orientation = KQuaternion.rotationAroundVector(dir_sgn * relTime * -90.0, KVector(1,0,0)) break when ACTION_CLIMB_DOWN - left_tire_rot += dir_sgn * dltTime - right_tire_rot += dir_sgn * dltTime - if (relTime <= 0.2) + @left_tire_rot += dir_sgn * dltTime + @right_tire_rot += dir_sgn * dltTime + if relTime <= 0.2 @current_position = @position + (relTime/0.2)/2 * @getDir() else if (relTime >= 0.8) - climb_orientation = KQuaternion::rotationAroundVector(dir_sgn * 90.0, KVector(1,0,0)) + @climb_orientation = KQuaternion.rotationAroundVector(dir_sgn * 90.0, KVector(1,0,0)) @current_position = @position + @getDir() + (0.5+(relTime-0.8)/0.2/2) * @getDown() else - climb_orientation = KQuaternion::rotationAroundVector(dir_sgn * (relTime-0.2)/0.6 * 90.0, KVector(1,0,0)) - rotVec = (orientation * climb_orientation).rotate(KVector(0.0, 1.0, 0.0)) + @climb_orientation = KQuaternion.rotationAroundVector(dir_sgn * (relTime-0.2)/0.6 * 90.0, KVector(1,0,0)) + rotVec = (orientation * @climb_orientation).rotate(KVector(0.0, 1.0, 0.0)) @current_position = @position.plus @getDir().plus(@getDown()).plus(rotVec).mul 0.5 break when ACTION_TURN_RIGHT, ACTION_TURN_LEFT - if (move_action == null and relTime == 0.0) # if not performing move action and start of rotation - # update orientation now, so next move action will move in desired direction - if (actionId == ACTION_TURN_LEFT) - orientation *= KQuaternion::rotationAroundVector(90.0, KVector(0,1,0)) - rest_orientation = KQuaternion::rotationAroundVector(-90.0, KVector(0,1,0)) + if @move_action == null and relTime == 0.0 # if not performing move action and start of rotation + # update @orientation now, so next move action will move in desired @direction + if actionId == ACTION_TURN_LEFT + @orientation *= KQuaternion.rotationAroundVector(90.0, KVector(0,1,0)) + @rest_orientation = KQuaternion.rotationAroundVector(-90.0, KVector(0,1,0)) else - orientation *= KQuaternion::rotationAroundVector(-90.0, KVector(0,1,0)) - rest_orientation = KQuaternion::rotationAroundVector(90.0, KVector(0,1,0)) + @orientation *= KQuaternion.rotationAroundVector(-90.0, KVector(0,1,0)) + @rest_orientation = KQuaternion.rotationAroundVector(90.0, KVector(0,1,0)) - if (actionId == ACTION_TURN_LEFT) - left_tire_rot += -dltTime - right_tire_rot += dltTime - rotate_orientation = KQuaternion::rotationAroundVector(relTime * 90.0, KVector(0,1,0)) + if actionId == ACTION_TURN_LEFT + @left_tire_rot += -dltTime + @right_tire_rot += dltTime + @rotate_orientation = KQuaternion.rotationAroundVector(relTime * 90.0, KVector(0,1,0)) else - left_tire_rot += dltTime - right_tire_rot += -dltTime - rotate_orientation = KQuaternion::rotationAroundVector(relTime * -90.0, KVector(0,1,0)) + @left_tire_rot += dltTime + @right_tire_rot += -dltTime + @rotate_orientation = KQuaternion.rotationAroundVector(relTime * -90.0, KVector(0,1,0)) break else - KikiPushable::performAction (action) + KikiPushable.performAction action return - @current_orientation = orientation * climb_orientation * rotate_orientation * rest_orientation + @current_orientation = @orientation * @climb_orientation * @rotate_orientation * @rest_orientation - finishAction: ( action ) -> + finishAction: (action) -> actionId = action.getId() - return if (actionId == ACTION_NOOP or actionId == ACTION_SHOOT) + return if actionId == ACTION_NOOP or actionId == ACTION_SHOOT - if (actionId == ACTION_PUSH) - KikiPushable::finishAction (action) + if actionId == ACTION_PUSH + KikiPushable.finishAction action return - if (actionId == ACTION_TURN_LEFT or actionId == ACTION_TURN_RIGHT) - rotate_action = null + if actionId == ACTION_TURN_LEFT or actionId == ACTION_TURN_RIGHT + @rotate_action = null - if (move_action) # bot currently performing a move action -> store rotation in rest_orientation - rest_orientation *= rotate_orientation - rotate_orientation.reset() + if move_action # bot currently performing a move action -> store rotation in @rest_orientation + @rest_orientation *= @rotate_orientation + @rotate_orientation.reset() else - orientation *= rotate_orientation * rest_orientation # update rotation matrix - rotate_orientation.reset() - rest_orientation.reset() - else if (actionId < ACTION_END) - move_action = null + @orientation *= @rotate_orientation * @rest_orientation # update rotation matrix + @rotate_orientation.reset() + @rest_orientation.reset() + else if actionId < ACTION_END + @move_action = null - orientation *= climb_orientation # update climb orientation - climb_orientation.reset() + @orientation *= @climb_orientation # update climb @orientation + @climb_orientation.reset() - if (rotate_action and actionId != ACTION_JUMP_FORWARD) # bot is currently performing a rotation -> + if @rotate_action and actionId != ACTION_JUMP_FORWARD # bot is currently performing a rotation -> # take over result of rotation to prevent sliding - if (rotate_action.getId() == ACTION_TURN_LEFT) - orientation *= KQuaternion::rotationAroundVector(90.0, KVector(0,1,0)) * rest_orientation - rest_orientation = KQuaternion::rotationAroundVector(-90.0, KVector(0,1,0)) + if @rotate_action.getId() == ACTION_TURN_LEFT + @orientation *= KQuaternion.rotationAroundVector(90.0, KVector(0,1,0)) * @rest_orientation + @rest_orientation = KQuaternion.rotationAroundVector(-90.0, KVector(0,1,0)) else - orientation *= KQuaternion::rotationAroundVector(-90.0, KVector(0,1,0)) * rest_orientation - rest_orientation = KQuaternion::rotationAroundVector(90.0, KVector(0,1,0)) + @orientation *= KQuaternion.rotationAroundVector(-90.0, KVector(0,1,0)) * @rest_orientation + @rest_orientation = KQuaternion.rotationAroundVector(90.0, KVector(0,1,0)) - if (actionId != ACTION_CLIMB_UP) + if actionId != ACTION_CLIMB_UP world.objectMovedFromPos @, @position # update world @position @position = @current_position.round() - if (actionId != ACTION_JUMP_FORWARD and rotate_action == null) # if not jumping forward - orientation *= rest_orientation # update rotation orientation - rest_orientation.reset() + if actionId != ACTION_JUMP_FORWARD and @rotate_action == null # if not jumping forward + @orientation *= @rest_orientation # update rotation @orientation + @rest_orientation.reset() - actionFinished: ( action ) -> + actionFinished: (action) -> actionId = action.getId() if @isDead() @@ -258,8 +258,8 @@ class Bot extends Pushable @startTimedAction getActionWithId(ACTION_NOOP), 0 return - if actionId == ACTION_PUSH or direction != KVector() - KikiPushable::actionFinished (action) + if actionId == ACTION_PUSH or @direction != KVector() + KikiPushable.actionFinished (action) return return if @move_action # action was not a move action -> return @@ -268,47 +268,47 @@ class Bot extends Pushable if actionId == ACTION_JUMP_FORWARD forwardPos = @position + @getDir() - if (world.isUnoccupiedPos(forwardPos)) + if world.isUnoccupiedPos forwardPos # forward will be empty - if (world.isUnoccupiedPos(forwardPos - @getUp())) + if world.isUnoccupiedPos forwardPos.minus @getUp() # below forward will also be empty - move_action = @getActionWithId (ACTION_FALL_FORWARD) - move_action.takeRest (action) + @move_action = @getActionWithId ACTION_FALL_FORWARD + @move_action.takeRest (action) else - move_action = @getActionWithId (ACTION_FORWARD) - playSoundAtPos(KikiSound::BOT_LAND, @getPos(), 0.25) + @move_action = @getActionWithId ACTION_FORWARD + playSoundAtPos(KikiSound.BOT_LAND, @getPos(), 0.25) else # forward will not be empty - if (world.isUnoccupiedPos(position - @getUp())) # below is empty - move_action = @getActionWithId (ACTION_CLIMB_UP) - playSoundAtPos(KikiSound::BOT_LAND, @getPos(), 0.5) - else if (world.isUnoccupiedPos(position - @getUp())) # below will be empty - if (move) # sticky if moving - if (world.isUnoccupiedPos(position + @getDir())) + if world.isUnoccupiedPos position.minus @getUp() # below is empty + @move_action = @getActionWithId ACTION_CLIMB_UP + playSoundAtPos KikiSound.BOT_LAND, @getPos(), 0.5 + else if world.isUnoccupiedPos position.minus @getUp() # below will be empty + if move # sticky if moving + if world.isUnoccupiedPos position.plus @getDir() # forward will be empty - if (world.isOccupiedPos (position + @getDir() - @getUp())) + if world.isOccupiedPos position.plus @getDir().minus @getUp() # below forward is solid - KikiObject * occupant = world.getOccupantAtPos(position + @getDir() - @getUp()) + KikiObject * occupant = world.getOccupantAtPos position.plus @getDir().minus @getUp() if occupant == null or not occupant.isSlippery() - move_action = @getActionWithId (ACTION_FORWARD) + @move_action = @getActionWithId (ACTION_FORWARD) else - KikiObject * occupant = world.getOccupantAtPos(position + @getDir()) + KikiObject * occupant = world.getOccupantAtPos position.plus @getDir() if occupant == null or not occupant.isSlippery() - move_action = @getActionWithId (ACTION_CLIMB_UP) + @move_action = @getActionWithId (ACTION_CLIMB_UP) - if move_action == null - move_action = @getActionWithId (ACTION_FALL) - move_action.takeRest (action) - else if (actionId == ACTION_FALL or actionId == ACTION_FALL_FORWARD) # landed + if @move_action == null + @move_action = @getActionWithId ACTION_FALL + @move_action.takeRest action + else if actionId == ACTION_FALL or actionId == ACTION_FALL_FORWARD # landed if @ == player - playSound KikiSound::BOT_LAND + playSound KikiSound.BOT_LAND else - playSoundAtPos KikiSound::BOT_LAND, @getPos() + playSoundAtPos KikiSound.BOT_LAND, @getPos() - if (move_action) - timer_event.addAction move_action + if @move_action + timer_event.addAction @move_action return - return if rotate_action + return if @rotate_action if move @moveBot() @@ -316,58 +316,58 @@ class Bot extends Pushable dir_sgn = 1.0 if actionId != ACTION_NOOP then jump_once = false # keep action chain flowing in order to detect environment changes - startTimedAction (getActionWithId (ACTION_NOOP), 0) + startTimedAction getActionWithId(ACTION_NOOP), 0 moveBot: () -> - move_action = null + @move_action = null KikiPos forwardPos = @position + @getDir() - if (jump or jump_once) and # jump mode or jump activated while moving - dir_sgn == 1.0 and # and moving forward - world.isUnoccupiedPos(position + @getUp()) # and above empty - if world.isUnoccupiedPos(forwardPos + @getUp()) and - world.isUnoccupiedPos(forwardPos) # forward and above forward also empty - move_action = @getActionWithId (ACTION_JUMP_FORWARD) - else # no space to jump forward -> jump up - move_action = @getActionWithId (ACTION_JUMP) - else if world.isUnoccupiedPos(forwardPos) # forward is empty - if world.isUnoccupiedPos(forwardPos + @getDown()) + if jump or jump_once and # jump mode or jump activated while moving + dir_sgn == 1.0 and # and moving forward + world.isUnoccupiedPos position.plus @getUp() # and above empty + if world.isUnoccupiedPos forwardPos.plus @getUp() and + world.isUnoccupiedPos forwardPos # forward and above forward also empty + @move_action = @getActionWithId ACTION_JUMP_FORWARD + else # no space to jump forward -> jump up + @move_action = @getActionWithId ACTION_JUMP + else if world.isUnoccupiedPos forwardPos # forward is empty + if world.isUnoccupiedPos forwardPos.plus @getDown() # below forward also empty - move_action = @getActionWithId ACTION_CLIMB_DOWN + @move_action = @getActionWithId ACTION_CLIMB_DOWN else # forward down is solid - move_action = @getActionWithId ACTION_FORWARD + @move_action = @getActionWithId ACTION_FORWARD else # forward is not empty moveAction = @getActionWithId ACTION_FORWARD if push and world.mayObjectPushToPos @, forwardPos, moveAction.getDuration() moveAction.reset() # player in push mode and pushing object is possible - if (world.isUnoccupiedPos(forwardPos + @getDown())) # below forward is empty - move_action = @getActionWithId ACTION_CLIMB_DOWN + if world.isUnoccupiedPos forwardPos.plus @getDown() # below forward is empty + @move_action = @getActionWithId ACTION_CLIMB_DOWN else - move_action = moveAction + @move_action = moveAction else # just climb up - move_action = @getActionWithId ACTION_CLIMB_UP + @move_action = @getActionWithId ACTION_CLIMB_UP # reset the jump once flag (either we jumped or it's not possible to jump at current @position) jump_once = false - if (move_action) - move_action.keepRest() # try to make subsequent actions smooth - timer_event.addAction move_action + if move_action + @move_action.keepRest() # try to make subsequent actions smooth + timer_event.addAction @move_action render: () -> - radius = 0.5 - tireRadius = 0.15 + radius = 0.5 + tireRadius = 0.15 - if (died) @getDeadColor().glColor() - else @getTireColor().glColor() + # if (@died) @getDeadColor().glColor() + # else @getTireColor().glColor() # KMatrix(current_orientation).glMultMatrix() # glPushMatrix() # tires # glRotated(90.0, 0.0, 1.0, 0.0) # glTranslated(0.0, 0.0, radius-tireRadius) - # glRotated(left_tire_rot * 180.0, 0.0, 0.0, 1.0) + # glRotated(@left_tire_rot * 180.0, 0.0, 0.0, 1.0) # # render_tire # @@ -375,7 +375,7 @@ class Bot extends Pushable # glPushMatrix() # glRotated(90.0, 0.0, 1.0, 0.0) # glTranslated(0.0, 0.0, -(radius-tireRadius)) - # glRotated(right_tire_rot * 180.0, 0.0, 0.0, 1.0) + # glRotated(@right_tire_rot * 180.0, 0.0, 0.0, 1.0) # # render_tire # @@ -383,14 +383,14 @@ class Bot extends Pushable # if not @died then @getBodyColor().glColor() - render_body + @render_body() - if (move_action or rotate_action) and died == false - unsigned now = getTime() - if ((int)(now - last_fume) > mapMsTime (40)) - fume = new KikiBotFume() - world.addObject fume - fume.setPosition @current_position - @getCurrentDir() * 0.4 - @last_fume = now + # if (@move_action or @rotate_action) and not @died + # unsigned now = getTime() + # if ((int)(now - last_fume) > mapMsTime (40)) + # fume = new KikiBotFume() + # world.addObject fume + # fume.setPosition @current_position - @getCurrentDir() * 0.4 + # @last_fume = now module.exports = Bot diff --git a/coffee/player.coffee b/coffee/player.coffee new file mode 100644 index 0000000..a0de6df --- /dev/null +++ b/coffee/player.coffee @@ -0,0 +1,474 @@ + +# 00000000 000 0000000 000 000 00000000 00000000 +# 000 000 000 000 000 000 000 000 000 000 +# 00000000 000 000000000 00000 0000000 0000000 +# 000 000 000 000 000 000 000 000 +# 000 0000000 000 000 000 00000000 000 000 + +Bot = require './bot' + +forward_key = "UP" +backward_key = "DOWN" +turn_left_key = "LEFT" +turn_right_key = "RIGHT" +shoot_key = "SPACE" +jump_key = "CTRL" +push_key = "SHIFT" +look_up_key = "HOME" +look_down_key = "END" +view_key = "PAGEDOWN" + +# KikiActionKey actionKeyMapping[] = + +class Player extends Bot + + constructor: -> + + super + + @look_action = null + @look_angle = 0.0 + @new_dir_sgn = 1.0 + @rotate = 0 + + @recorder = null + @playback = null + + # @flags[KDL_KEYHANDLER_FLAG_HANDLES_RELEASE] = true + + @addAction new KikiAction @, ACTION_LOOK_UP, "look up", 220 + @addAction new KikiAction @, ACTION_LOOK_DOWN, "look down", 220 + @addAction new KikiAction @, ACTION_LOOK_RESET, "look reset", 60 + + @addEventWithName "keyset" + @addEventWithName "keyset failed" + @addEventWithName "landed" + + # @projection = new KLightingProjection (90.0) + # @projection.updateViewport() + # @projection.getLight().setCutoff (90.0) + # @projection.getLight().setAttenuation (1.0, 0.0, 0.05) + + # Controller.player_status->setStatus status + + + getActionForKey: (keyName) -> + index = 0 + while actionKeyMapping[index].actionName + if keyName == actionKeyMapping[index].keyName + return actionKeyMapping[index].actionName + index++ + + return "" + + getKeyForAction: (actionName) -> + index = 0 + while actionKeyMapping[index].actionName + if actionName == actionKeyMapping[index].actionName + return actionKeyMapping[index].keyName + index++ + + return "" + + setKeyForAction: (keyName, actionName) -> + index = 0 + while actionKeyMapping[index].actionName + if actionName == actionKeyMapping[index].actionName + actionKeyMapping[index].keyName = keyName + index++ + + recordKeyForAction: (actionName) -> + RecordingActionName = actionName + KeyRecorder.startRecordingSequence @, @setRecordedKey, 1 + + setRecordedKey: (keyName) -> + index = 0 + while (actionKeyMapping[index].actionName) + if keyName == actionKeyMapping[index].keyName and actionKeyMapping[index].actionName != RecordingActionName + setKeyForAction "", actionKeyMapping[index].actionName + index += 1 + setKeyForAction keyName, RecordingActionName + getEventWithName("keyset").triggerActions() + + updatePosition: () -> + if (move_action) + relTime = (Controller.getTime() - move_action.getStart()) / move_action.getDuration() + if (relTime <= 1.0) + switch move_action.getId() + when ACTION_FORWARD + current_position = position + relTime * getDir() + when ACTION_FALL + current_position = position - relTime * getUp() + when ACTION_JUMP_FORWARD + current_position = position + (1.0 - Math.cos(Math.PI/2 * relTime)) * getDir() + Math.cos(Math.PI/2 - Math.PI/2 * relTime) * getUp() + when ACTION_FALL_FORWARD + current_position = position + Math.cos(Math.PI/2 - Math.PI/2 * relTime) * getDir() + (1.0 - Math.cos(Math.PI/2 * relTime)) * -getUp() + + getProjection: () -> + # smooth camera movement a little bit + posDelta = Controller.getSpeed() / 10.0 + projection.setPosition ((1.0 - posDelta) * projection.getPosition() + posDelta * current_position) + + KVector playerDir = getCurrentDir() + KVector playerUp = current_orientation.rotate (KVector(0,1,0)).normal() + + if (look_angle) # player is looking up or down + projection.setXVector (playerUp.cross (playerDir).normal()) + look_rot = KQuaternion.rotationAroundVector look_angle, projection.getXVector() + projection.setYVector (look_rot.rotate (playerUp)) + projection.setZVector (look_rot.rotate (-playerDir)) + else + # smooth camera rotation a little bit + lookDelta = (2.0 - projection.getZVector() * playerDir) * Controller.getSpeed() / 50.0 + KVector newLookVector = (1.0 - lookDelta) * projection.getZVector() - lookDelta * playerDir + newLookVector.normalize() + + projection.setXVector (playerUp.cross(newLookVector).normal()) + projection.setYVector (playerUp) + projection.setZVector (newLookVector) + + return projection + + getBehindProjection: () -> + updatePosition() + + playerDir = getCurrentDir() + playerUp = current_orientation.rotate(KVector(0,1,0)).normal() + + # find a valid camera position + botToCamera = (playerUp - 2 * playerDir) + min_f = botToCamera.length() + botToCamera.normalize() + + min_f = Math.min world.getWallDistanceForRay(current_position, botToCamera), min_f + cameraPos = current_position + kMax(min_f, 0.72) * botToCamera + cameraPos = world.getInsideWallPosWithDelta cameraPos, 0.2 + + # smooth camera movement a little bit + posDelta = 0.2 + projection.setPosition ((1.0 - posDelta) * projection.getPosition() + posDelta * cameraPos) + + if (look_angle) + projection.setXVector(playerUp.cross(playerDir).normal()) + KQuaternion look_rot = KQuaternion.rotationAroundVector(look_angle, projection.getXVector()) + projection.setYVector(look_rot.rotate(playerUp)) + projection.setZVector(look_rot.rotate(-playerDir)) + else + # smooth camera rotation a little bit + lookDelta = 0.3 + KVector newLookVector =(1.0 - lookDelta) * projection.getZVector() - lookDelta * playerDir + newLookVector.normalize() + + projection.setZVector(newLookVector) + projection.setXVector(playerUp.cross(newLookVector).normal()) + projection.setYVector(newLookVector.cross(projection.getXVector()).normal()) + + return projection + + getFollowProjection: () -> + cameraPos = projection.getPosition() # current camera position + desiredDistance = 2.0 # desired distance from camera to bot + + updatePosition() + + playerPos = current_position # desired look pos + playerDir = getCurrentDir() + playerUp = current_orientation.rotate(KVector(0,1,0)).normal() + playerRight = playerDir.cross(playerUp).normal() + + # .................................................................. camera follows bot + # first, adjust distance from camera to bot + + botToCamera = cameraPos - playerPos # vector from bot to current pos + cameraBotDistance = botToCamera.length() # distance from camera to bot + + if cameraBotDistance >= desiredDistance + difference = cameraBotDistance - desiredDistance + delta = (difference*difference)/400.0 # weight for following speed + cameraPos = (1.0 - delta) * cameraPos + delta * playerPos + else + difference = desiredDistance - cameraBotDistance + delta = difference/20.0 # weight for negative following speed + cameraPos = (1.0 - delta) * cameraPos + delta * (playerPos + desiredDistance * botToCamera.normal()) + + # .................................................................. refining camera position + # second, rotate around bot + + botToCamera = cameraPos - playerPos + KVector botToCameraNormal = botToCamera.normal() + + # .................................................................. try view bot from above + # if camera below bot, rotate up + if (botToCameraNormal * playerUp < 0) + # calculate angle between player to camera vector and player up vector + verticalAngle = RAD2DEG (acos (kMinMax(-1.0, 1.0, botToCameraNormal * playerUp))) - 90.0 + cameraPos = playerPos + KQuaternion.rotationAroundVector(verticalAngle/40.0, botToCameraNormal.cross(playerUp)).rotate(botToCamera) + + botToCamera = cameraPos - playerPos + botToCameraNormal = (cameraPos - playerPos).normal() + + rot_factor = 1.0 + wall_distance = world.getWallDistanceForPos (playerPos + botToCamera) + if wall_distance < 0.5 + # .................................................................. apiercing walls + + if (wall_distance < 0.2) + cameraPos = world.getInsideWallPosWithDelta cameraPos, 0.2 + botToCamera = cameraPos - playerPos + botToCameraNormal = (cameraPos - playerPos).normal() + + rot_factor = 0.5 / (wall_distance-0.2) + + # .................................................................. try view bot from behind + # calculate horizontal angle between bot orientation and vector to camera + mappedToXZ ((botToCamera - playerUp * (botToCamera * playerUp)).normal()) + horizontalAngle = RAD2DEG (acos (kMinMax(-1.0, 1.0, -playerDir * mappedToXZ))) + if (botToCameraNormal * playerRight > 0) + horizontalAngle = -horizontalAngle + + cameraPos = playerPos + KQuaternion.rotationAroundVector(horizontalAngle / (rot_factor * 400.0), playerUp).rotate botToCamera + + botToCamera = cameraPos - playerPos + botToCameraNormal = botToCamera.normal() + + # .................................................................. finally, set the position + + projection.setPosition cameraPos + + # .................................................................. refining camera orientation + + # slowly adjust look direction by interpolating current and desired directions + lookDelta = 2.0 - projection.getZVector() * botToCameraNormal + lookDelta *= lookDelta / 30.0 + KVector newLookVector = (1.0 - lookDelta) * projection.getZVector() + lookDelta * botToCameraNormal + newLookVector.normalize() + + # slowly adjust up vector by interpolating current and desired up vectors + upDelta = 2.0 - projection.getYVector() * playerUp + upDelta *= upDelta / 100.0 + KVector newRightVector = ((1.0 - upDelta) * projection.getYVector() + upDelta * playerUp).cross(newLookVector) + newRightVector.normalize() + KVector newUpVector = newLookVector.cross(newRightVector).normal() + + # finished interpolations, update camera matrix + projection.setZVector (newLookVector) + projection.setXVector (newRightVector) + projection.setYVector (newUpVector) + + return projection + + initAction: (action) -> + actionId = action.getId() + switch actionId + when ACTION_CLIMB_DOWN, ACTION_FORWARD + status.addMoves(1) + when ACTION_TURN_LEFT, ACTION_TURN_RIGHT + Controller.sound.playSound(KikiSound.BOT_MOVE) + when ACTION_JUMP, ACTION_JUMP_FORWARD + status.addMoves(actionId == ACTION_JUMP ? 1 : 2) + Controller.sound.playSound(KikiSound.BOT_JUMP) + + KikiBot.initAction(action) + + performAction: (action) -> + relTime = action.getRelativeTime() + + switch (action.getId()) + when ACTION_NOOP then return + + when ACTION_LOOK_UP + look_angle = relTime * -90.0 + + when ACTION_LOOK_DOWN + look_angle = relTime * 90.0 + + when ACTION_LOOK_RESET + if look_angle > 0 + look_angle = Math.min look_angle, (1.0-relTime) * 90.0 + else + look_angle = Math.max look_angle, (1.0-relTime) * -90.0 + else + KikiBot.performAction(action) + + finishAction: (action) -> + actionId = action.getId() + + if (actionId == ACTION_LOOK_RESET) + look_action = null + look_angle = 0.0 + else + if (action == move_action) # move finished, update direction + dir_sgn = new_dir_sgn + + if (actionId != ACTION_LOOK_UP and actionId != ACTION_LOOK_DOWN) + KikiBot.finishAction(action) + + if (actionId == ACTION_TURN_LEFT or actionId == ACTION_TURN_RIGHT) + if (rotate) + rotate_action = getActionWithId (rotate) + rotate_action.reset() + Controller.timer_event.addAction (rotate_action) + + die: () -> + Controller.removeKeyHandler (this) + KikiBot.die() + Controller.displayText("game over") + Controller.sound.playSound (KikiSound.BOT_DEATH) + world.setCameraMode (KikiWorld.CAMERA_FOLLOW) + + reborn: () -> + Controller.addKeyHandler (this) + died = false + + reset: () -> + KikiBot.reset() + Controller.timer_event.removeActionsOfObject * + + look_action = null + look_angle = 0.0 + new_dir_sgn = 1.0 + rotate = 0 + + recorder = null + playback = null + + saveRecorder: () -> + if @recorder + @recorder.save() + @recorder = null + + startRecorder: (file) -> + if @recorder + saveRecorder() + @recorder = new KikiRecorder file + + #define KEY_HANDLED if (recorder) recorder->recordKey (key) return true + + handleKey: (key) -> + keyName = key.getUnmodifiedName() + + if (keyName == forward_key or keyName == backward_key) + move = true # try to move as long as the key is not released + + if (move_action == null) # player is currently not performing a move action + # forward or backward direction + new_dir_sgn = dir_sgn = (key.getUnmodifiedName() == backward_key) ? -1 : 1 + + moveBot() # perform new move action (depending on environment) + else + new_dir_sgn = (keyName == backward_key) ? -1 : 1 + + KEY_HANDLED + + if (keyName == turn_left_key or keyName == turn_right_key) + rotate = (keyName == turn_left_key) ? ACTION_TURN_LEFT : ACTION_TURN_RIGHT + + if (rotate_action == null and spiked == false) # player is not performing a rotation and unspiked + rotate_action = getActionWithId rotate + Controller.timer_event.addAction rotate_action + + KEY_HANDLED + + if key.name == jump_key + jump = true # switch to jump mode until jump_key released + jump_once = true + KEY_HANDLED + + if key.name == push_key + push = true + KEY_HANDLED + + if keyName == shoot_key + if (shoot == false) + shoot = true + Controller.timer_event.addAction (getActionWithId (ACTION_SHOOT)) + + KEY_HANDLED + + if keyName == look_up_key or keyName == look_down_key + if (!look_action) + look_action = getActionWithId ((key.name == look_up_key) ? ACTION_LOOK_UP : ACTION_LOOK_DOWN) + look_action.reset() + Controller.timer_event.addAction (look_action) + KEY_HANDLED + + if keyName == view_key + world.changeCameraMode() + KEY_HANDLED + + return false + + #define KEY_RELEASE_HANDLED if (recorder) recorder->recordKeyRelease (key) return true + + handleKeyRelease: (key) -> + keyName = key.getUnmodifiedName() + + if (keyName == shoot_key) + Controller.timer_event.removeAction (getActionWithId(ACTION_SHOOT)) + shoot = false + KEY_RELEASE_HANDLED + + if (keyName == forward_key or keyName == backward_key) + move = false + KEY_RELEASE_HANDLED + + if key.name == jump_key + jump = false + if jump_once + if (move_action == null and world.isUnoccupiedPos (position + getUp())) + jump_once = false + move_action = getActionWithId (ACTION_JUMP) + Controller.sound.playSound (KikiSound.BOT_JUMP) + Controller.timer_event.addAction (move_action) + KEY_RELEASE_HANDLED + + if keyName == turn_left_key or keyName == turn_right_key + rotate = 0 + KEY_RELEASE_HANDLED + + if key.name == push_key + push = false + KEY_RELEASE_HANDLED + + if keyName == look_down_key or keyName == look_up_key + if (look_action and look_action.getId() != ACTION_LOOK_RESET) + Controller.timer_event.removeAction (look_action) + look_action = getActionWithId (ACTION_LOOK_RESET) + Controller.timer_event.addAction (look_action) + KEY_RELEASE_HANDLED + + if keyName == view_key + KEY_RELEASE_HANDLED + + return false + + display: () -> + if world.getCameraMode() != KikiWorld.CAMERA_INSIDE or world.getEditMode() + # glPushMatrix() + # current_position.glTranslate() + render() + # glPopMatrix() + + getBodyColor: () -> + if world.getCameraMode() == KikiWorld.CAMERA_BEHIND + # static bodyColor + bodyColor = colors[KikiPlayer_base_color] + bodyColor.setAlpha(kMin(0.7, (projection.getPosition()-current_position).length()-0.4)) + return bodyColor + + return colors[KikiPlayer_base_color] + + getTireColor: () -> + if world.getCameraMode() == KikiWorld.CAMERA_BEHIND + # static tireColor + tireColor = colors[KikiPlayer_tire_color] + tireColor.setAlpha(kMin(1.0, (projection.getPosition()-current_position).length()-0.4)) + return tireColor + + return colors[KikiPlayer_tire_color] + + finishRotateAction: () -> + if (rotate_action) + rotate = false + finishAction(rotate_action) +