kiki/coffee/bot.coffee
monsterkodi 9372c1f3a6 bot
2016-08-10 23:53:20 +02:00

397 lines
16 KiB
CoffeeScript

# 0000000 0000000 000000000
# 000 000 000 000 000
# 0000000 000 000 000
# 000 000 000 000 000
# 0000000 0000000 000
Pushable = require './pushable'
class Bot extends Pushable
constructor: () ->
@left_tire_rot = 0.0
@right_tire_rot = 0.0
@last_fume = 0
@status = new KikiBotStatus @
@move = false
@push = false
@jump = false
@shoot = false
@jump_once = false
@spiked = false
@died = false
@move_action = null
@rotate_action = null
@dir_sgn = 1.0
@addAction new KikiAction @, ACTION_NOOP, "noop", 0
@addAction new KikiAction @, ACTION_FORWARD, "move forward", 200
@addAction new KikiAction @, ACTION_CLIMB_UP, "climb up", 200
@addAction new KikiAction @, ACTION_CLIMB_DOWN, "climb down", 500
@addAction new KikiAction @, ACTION_TURN_LEFT, "turn left", 200
@addAction new KikiAction @, ACTION_TURN_RIGHT, "turn right", 200
@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
@getActionWithId(ACTION_FALL).setDuration 120
@addEventWithName "died"
@startTimedAction @getActionWithId ACTION_NOOP, 500
die: () ->
# timer_event.removeActionsOfObject (@)
@move = false
@jump = false
@shoot = false
@push = false
@getEventWithName("died").triggerActions()
@died = true
reset: () ->
@left_tire_rot = 0.0
@right_tire_rot = 0.0
@last_fume = 0
@direction.reset()
@orientation.reset()
@current_orientation.reset()
@rotate_orientation.reset()
@climb_orientation.reset()
@rest_orientation.reset()
@move_action = null
@move = false
@push = false
@jump = false
@shoot = false
@jump_once = false
@spiked = false
@died = false
isFalling: -> @move_action and @move_action.getId() == ACTION_FALL
initAction: (action) ->
newPos = new KikiPos @position
switch action.getId()
when ACTION_NOOP then return
when ACTION_FORWARD then newPos += @getDir()
when ACTION_CLIMB_DOWN then newPos += @getDir() + @getDown()
when ACTION_JUMP then newPos += @getUp()
when ACTION_JUMP_FORWARD then newPos += @getUp() + @getDir()
when ACTION_FALL_FORWARD then newPos += @getDown() + @getDir()
when ACTION_FALL
if @direction != KVector()
KikiPushable::initAction action
return
else
newPos += @getDown()
break
else
KikiPushable::initAction (action)
return
# if newPos != @position
# world.objectWillMoveToPos (@, newPos, action.getDuration())
performAction: ( action ) ->
actionId = action.getId()
relTime = action.getRelativeTime()
dltTime = action.getRelativeDelta()
switch actionId
when ACTION_SHOOT
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
@current_position = @position + relTime * @getDir()
return
when ACTION_JUMP
@current_position = @position + Math.cos(Math.PI/2 - Math.PI/2 * relTime) * @getUp()
return
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)
@current_position = @position + (1.0 - Math.cos(Math.PI/2 * relTime)) * @getDir() + Math.cos(Math.PI/2 - Math.PI/2 * relTime) * @getUp()
return
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)) * @getDown()
return
when ACTION_FALL
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))
break
when ACTION_CLIMB_DOWN
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))
@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))
@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))
else
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))
else
left_tire_rot += dltTime
right_tire_rot += -dltTime
rotate_orientation = KQuaternion::rotationAroundVector(relTime * -90.0, KVector(0,1,0))
break
else
KikiPushable::performAction (action)
return
@current_orientation = orientation * climb_orientation * rotate_orientation * rest_orientation
finishAction: ( action ) ->
actionId = action.getId()
return if (actionId == ACTION_NOOP or actionId == ACTION_SHOOT)
if (actionId == ACTION_PUSH)
KikiPushable::finishAction (action)
return
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()
else
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()
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))
else
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)
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()
actionFinished: ( action ) ->
actionId = action.getId()
if @isDead()
die() if not @died
if actionId != ACTION_PUSH and actionId != ACTION_FALL
# dead player may only fall, nothing else
return
if spiked
@move_action = null
@startTimedAction getActionWithId(ACTION_NOOP), 0
return
if actionId == ACTION_PUSH or direction != KVector()
KikiPushable::actionFinished (action)
return
return if @move_action # action was not a move action -> return
# find next action depending on type of finished action and surrounding environment
if actionId == ACTION_JUMP_FORWARD
forwardPos = @position + @getDir()
if (world.isUnoccupiedPos(forwardPos))
# forward will be empty
if (world.isUnoccupiedPos(forwardPos - @getUp()))
# below forward will also be empty
move_action = @getActionWithId (ACTION_FALL_FORWARD)
move_action.takeRest (action)
else
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()))
# forward will be empty
if (world.isOccupiedPos (position + @getDir() - @getUp()))
# below forward is solid
KikiObject * occupant = world.getOccupantAtPos(position + @getDir() - @getUp())
if occupant == null or not occupant.isSlippery()
move_action = @getActionWithId (ACTION_FORWARD)
else
KikiObject * occupant = world.getOccupantAtPos(position + @getDir())
if occupant == null or not occupant.isSlippery()
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 @ == player
playSound KikiSound::BOT_LAND
else
playSoundAtPos KikiSound::BOT_LAND, @getPos()
if (move_action)
timer_event.addAction move_action
return
return if rotate_action
if move
@moveBot()
else
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)
moveBot: () ->
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())
# below forward also empty
move_action = @getActionWithId ACTION_CLIMB_DOWN
else # forward down is solid
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
else
move_action = moveAction
else # just 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
render: () ->
radius = 0.5
tireRadius = 0.15
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)
#
# render_tire
#
# glPopMatrix()
# 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)
#
# render_tire
#
# glPopMatrix()
# if not @died then @getBodyColor().glColor()
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
module.exports = Bot